美文网首页
FFMPEG解码学习

FFMPEG解码学习

作者: iOS_tree | 来源:发表于2019-01-24 15:01 被阅读0次

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
欢迎大家留意交流。

相关文章

网友评论

      本文标题:FFMPEG解码学习

      本文链接:https://www.haomeiwen.com/subject/kigyjqtx.html