1、注册组件
在调用相关解码代码前,我们需要注册相关组件进行开发,代码如下
av_register_all(); //注册所有支持的格式
avcodec_register_all(); //注册编解码器
avformat_network_init(); //注册网络格式,如果为本地文件则可以去掉该代码
FFMPEG支持的格式非常广泛,我们常见的mov、MP4、avi等文件格式都支持,网络格式也支持rtmp、rtsp等格式。
void av_register_all(void);
/**
* Initialize libavformat and register all the muxers, demuxers and
* protocols. If you do not call this function, then you can select
* exactly which formats you want to support.
初始化 libavformat 和注册所有的muxers,demuxers和协议.假如你不调用这个方法,那么你可以选择你想要支持的格式。
*
* @see av_register_input_format()
* @see av_register_output_format()
*/
void av_register_all(void);
/**
* Register all the codecs, parsers and bitstream filters which were enabled at
* configuration time. If you do not call this function you can select exactly
* which formats you want to support, by using the individual registration
* functions.
*
* @see avcodec_register
* @see av_register_codec_parser
* @see av_register_bitstream_filter
注册所有的codec,parser(解析器)和bitstream filter(过滤器)在启用配置的时候。如果你不调用这个方法,你可以选择准确的你想支持的格式,通过使用单个的注册的函数。
*/
void avcodec_register_all(void);
/**
* Do global initialization of network components. This is optional,
* but recommended, since it avoids the overhead of implicitly
* doing the setup for each session.
*
* Calling this function will become mandatory if using network
* protocols at some major version bump.
一个全局的网络组件的初始化。这是可选的,但是记住,他会为每个会话避免隐形的开销。
调用这个函数将会变成强制性的,假如在一些主要的版本使用网络协议。
*/
int avformat_network_init(void);
2、打开文件或者网络流
FFMPEG支持众多的文件格式,我们在播放时只需使用相关函数即可打开文件或者网络流,函数如下:
/**
* Open an input stream and read the header. The codecs are not opened.
* The stream must be closed with avformat_close_input().
*
* @param ps Pointer to user-supplied AVFormatContext (allocated by avformat_alloc_context).
* May be a pointer to NULL, in which case an AVFormatContext is allocated by this
* function and written into ps.
* Note that a user-supplied AVFormatContext will be freed on failure.
* @param url URL of the stream to open.
* @param fmt If non-NULL, this parameter forces a specific input format.
* Otherwise the format is autodetected.
* @param options A dictionary filled with AVFormatContext and demuxer-private options.
* On return this parameter will be destroyed and replaced with a dict containing
* options that were not found. May be NULL.
*
* @return 0 on success, a negative AVERROR on failure.
*
* @note If you want to use custom IO, preallocate the format context and set its pb field.
*
* 打开一个输入的流,并读取头部。这个编解码器不会被打开。
*
* 这个流必须与avformat_close_input()一起关闭。
*
* ps参数:指针指向用户提供的AVFormatContext结构体(通过avformat_alloc_context分配的结构体)。可以指向NULL,在这种情况下,通过这个方法来分配一个AVFormatContext并写到ps中。注意:用户提供的AVFormatContext如果失败则会被释放。
*
* url参数:打开流文件的URL
*
* fmt参数:假如这个参数不是NULL,这个参数就会强制输入这个格式。否则就会自动检测。
*
* options参数:充满着AVFormatContext和demuxer-private选项的字典。当这个函数返回时,这个参数也会被释放,并且这个字典包含的选项也会消失。这个可以为NULL
*
* return值:0代表成功,其他的AVERROR则代表失败
*
* 注意:假如你想自定义IO,请预分配这个上下文并设置pd字段。
*/
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
返回结果为0则代表执行成功。
3、查询相关音视频流信息
函数如下:
/**
* Read packets of a media file to get stream information. This
* is useful for file formats with no headers such as MPEG. This
* function also computes the real framerate in case of MPEG-2 repeat
* frame mode.
* The logical file position is not changed by this function;
* examined packets may be buffered for later processing.
*
* @param ic media file handle
* @param options If non-NULL, an ic.nb_streams long array of pointers to
* dictionaries, where i-th member contains options for
* codec corresponding to i-th stream.
* On return each dictionary will be filled with options that were not found.
* @return >=0 if OK, AVERROR_xxx on error
*
* @note this function isn't guaranteed to open all the codecs, so
* options being non-empty at return is a perfectly normal behavior.
*
* @todo Let the user decide somehow what information is needed so that
* we do not waste time getting stuff the user does not need.
*
* 读取一个媒体文件的packets来得到流信息。这个对于没有headers的文件流来说是很有用的,比如MPEG。这个函数也会在MPEG-2重复帧的模式下计算真的帧速率。
*
* 这个逻辑文件位置不会因为这个方法而发生改变。检查的packets可能会被缓存,供以后使用。
*
* ic参数:media文件的句柄
*
* options参数:假如不为NULL,一个ic.nb_streams的指向字典的长数组,i-th成员包含着编解码器对应流的i-th选项。在函数返回时,每一个字典将会被填满没有被找到的选项。
*
* return值:大于等于0则代表成功,AVERROR_xxx则是失败
*
* 注意:这个函数不能担保打开所有的编解码器,所以options在函数返回时非空是正常的行为
*
* todo:让用户决定哪些消息是被需要的,所以我们不会浪费时间来得到用户不需要的东西。
*/
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
在读取基本信息后,查找视频流和音频流在文件中相关的位置下标,代码如下:
//查找视频流
int videoStreamID = -1;
for (int i = 0; i < _formatContext->nb_streams; i++) {
if (_formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStreamID = i;
break;
}
}
//查找音频流
int audioStreamID = -1;
for (int i = 0; i < _formatContext->nb_streams; i++) {
if (_formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audioStreamID = i;
break;
}
}
查找并记录相关流的下标。
根据音视频流的下标,获取相关流对象:
AVCodecParameters *videoParameters = _formatContext->streams[videoStreamID]->codecpar;
AVStream *videoStream = _formatContext->streams[videoStreamID];
AVCodecParameters *audioCodecParameters = _formatContext->streams[audioStreamID]->codecpar;
AVStream *audioStream = _formatContext->streams[audioStreamID];
4、获取及打开解码器
获取解码器及设置解码器上下文参数,代码如下:
AVCodec *Videocodec = avcodec_find_decoder(videoStream->codecpar->codec_id);
_videoCodecContext = avcodec_alloc_context3(Videocodec);
AVCodec *audioCodec = avcodec_find_decoder(audioStream->codecpar->codec_id);
_audioCodeContext = avcodec_alloc_context3(audioCodec);
if((result = avcodec_parameters_to_context(_videoCodecContext, videoStream->codecpar)) < 0) {
printf("copy the codec parameters to context fail, err code : %d\n", result);
NSError *error = [NSError EC_errorWithLocalizedDescription:[NSString stringWithFormat:@"copy the codec parameters to context fail, err code : %d\n", result]];
failure(error);
return;
}
if ((result = avcodec_parameters_to_context(_audioCodeContext, audioStream->codecpar)) < 0) {
printf("copy the codec parameters to context fail, err code : %d\n", result);
NSError *error = [NSError EC_errorWithLocalizedDescription:[NSString stringWithFormat:@"copy the codec parameters to context fail, err code : %d\n",result]];
failure(error);
return;
}
最后打开解码器,准备解码,代码如下:
if((result = avcodec_open2(_videoCodecContext, Videocodec, NULL)) < 0) {
printf("open codec fail , err code : %d", result);
NSError *error = [NSError EC_errorWithLocalizedDescription:[NSString stringWithFormat:@"open codec fail , err code : %d", result]];
failure(error);
return;
}
if ((result = avcodec_open2(_audioCodeContext, audioCodec, NULL)) < 0) {
printf("open codec fail , err code : %d", result);
NSError *error = [NSError EC_errorWithLocalizedDescription:[NSString stringWithFormat:@"open codec fail , err code : %d", result]];
failure(error);
return;
}
5、读取音频或者视频压缩数据
读取音频或者视频压缩数据代码的函数如下:
/**
* Return the next frame of a stream.
* This function returns what is stored in the file, and does not validate
* that what is there are valid frames for the decoder. It will split what is
* stored in the file into frames and return one for each call. It will not
* omit invalid data between valid frames so as to give the decoder the maximum
* information possible for decoding.
*
* If pkt->buf is NULL, then the packet is valid until the next
* av_read_frame() or until avformat_close_input(). Otherwise the packet
* is valid indefinitely. In both cases the packet must be freed with
* av_packet_unref when it is no longer needed. For video, the packet contains
* exactly one frame. For audio, it contains an integer number of frames if each
* frame has a known fixed size (e.g. PCM or ADPCM data). If the audio frames
* have a variable size (e.g. MPEG audio), then it contains one frame.
*
* pkt->pts, pkt->dts and pkt->duration are always set to correct
* values in AVStream.time_base units (and guessed if the format cannot
* provide them). pkt->pts can be AV_NOPTS_VALUE if the video format
* has B-frames, so it is better to rely on pkt->dts if you do not
* decompress the payload.
*
* @return 0 if OK, < 0 on error or end of file
*
* 从一个流中返回下一帧。
* 这个方法返回的是存在文件中的东西,并且不会验证这个对于解码器来说是否是有效的。他会分开这些存储在文件中的帧,并且在每次调用时返回一帧。他不会忽略到在可用的帧中存在的不可用的数据,保证给解码器最大的可以用到的解码信息。
* 假如pct->buf是NULL,然后这个packet是有效的,直到下一个av_read_frame()或者avformat_close_input()才会失效;否则packet就会永远有效。在这两种情况下,当packet长时间不再使用时必须使用av_packet_unref进行释放。对于video,这个packet包含精确的帧。对于audio,他讲包含一个整数的帧,假如每个帧有一个已知的固定大小(例如MPEG audio),他就会包含一个帧。
* 在AVStream的time_base units(基本时间单位中)(假如这个format不能够正确提供这些,则猜测他们)中 pkt->pts,pkt->dts ,pkt->duration 这几个属性总是设置正确的值。pkt->pts可以是AV_NOPTS_VALUE 假如这个video格式有B帧,所以如果你不加压缩有效的负载则最好依赖于 pkt->dts;
* return:返回0则代表成功,否则错误
*/
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
6、解码音频或视频数据
新版的FFMPEG解码函数由两个组成,调用如下:
result = avcodec_send_packet(_videoCodecContext, model.packet);
AVFrame *videoFrame = av_frame_alloc();
result = avcodec_receive_frame(_videoCodecContext, videoFrame);
第一个函数为发送压缩文件到解码器;第二个函数为创建一个frame对象接收解码后的数据;第三个函数为接收解码后的音视频数据。
到此,一个简单的编解码器完成。由于本人写的比较简略,需要详细深入了解的可下载相关代码进行阅读。
GitHub Demo地址:https://github.com/XMSECODE/FFMPEG_STUDY
欢迎大家留意交流。
网友评论