ffmpeg(九)MP4/MP3解封装

这把我C 2021-04-27 16:32:49 5246

ffmpeg(九)MP4/MP3解封装

前言

解封装包括很多层步骤,包括协议的解析,封装格式的解析。ffmpeg中,本地文件当做file://协议来解析,远程文件采用的传输协议有http(s),rtsp等等。封装格式比如MP4,MOV,TS,MPEG等等。对于ffmpeg来说,只需要调用一个借口函数即可完成解封装的所有步骤,非常简单

解封装相关流程

img点击并拖拽以移动

解封装相关函数介绍

  • 1、AVFormatContext重要字段介绍(针对解封装后的)

nb_streams:包含的流的个数
streams:每个流对象,流对象中包括音视频编码参数信息;具体存储在AVStream中AVCodecParameters对象里面
metadata:解封装对应格式的标签信息

AVCodecParameters
codec_type:表示数据类型,音频数据或者视频数据
codec_id:音频或者视频采用的编码器
format:音频,采样格式;视频,像素格式
width/height:视频宽高
color_range:视频颜色范围;AVCOL_RANGE_MPEG:代表以BT601,BT709,BT2020等以广播电视系统类的颜色取值范围类型;AVCOL_RANGE_JPEG:代表以电脑等显示器类的颜色取值范围
color_space:颜色空间;比如RGB,YUV,CMYK等等
channel_layout:音频的声道类型
channels:音频的声道数
sample_rate:音频的采样率
frame_size:音频编码器设置的frame size大小

  • 2、int avformat_open_input(AVFormatContext ps,const char url,ff_const59 AVInputFormat fmt,AVDictionary options)

根据给定的url和fmt,options参数进行执行解封装,最终解封装的相关参数填入到ps中去,成功则返回0,失败则返回负数;

  • 3、int avformat_find_stream_info(AVFormatContext *ps,AVDictionary **options)

查找流信息,并尝试读取一个数据包,将编解码参数赋值给AVFormatContext;
成功返回0,失败返回负数

  • 4、int av_dump_info(AVFormatContext ps,int index,const char url,int is_output)

打印解封装后的参数信息

  • 5、AVFormatContext *avformat_alloc_context(void)

创建一个AVFormatContext对象,并赋值为初始值,不包含编码相关参数

  • 6、AVIOContext avio_alloc_context(
    unsigned char
    buff,
    int buffer_size,
    int write_flag,
    void opaque,
    int (
    read_packet)(void opaque,uint8_t buffer,int buf_size),
    int (write_packet)(void opaque,uint8_t buffer,int buf_size),
    int64_t (
    seek)(void *opaque,int64_t offset,int whence)
    )

创建一个AVIOContext对象,该对象创建完毕后赋值给AVFormatContext的pb对象。该赋值必须再avformat_open_input()函数调用之前完成;默认情况下,avformat_open_input()函数内部会自己创建pb和AVFormatContext对象,也可以用步骤5和6提前自定义

  • 7、avformat_close_input(AVFormatContext **ps)

关闭AVFormatContext上下文

  • 8、avio_context_free()

关闭AVIOContext上下文

实现代码

头文件

//
//  demuxer.hpp
//  video_encode_decode
//
//  Created by apple on 2020/3/24.
//  Copyright © 2020 apple. All rights reserved.
//

#ifndef demuxer_hpp
#define demuxer_hpp

#include <stdio.h>
#include <string>
#include "CLog.h"
extern "C"{
#include <libavutil/imgutils.h>
#include <libavformat/avformat.h>
#include <libavutil/error.h>
#include <libavutil/file.h>
#include <libavformat/avio.h>
}
using namespace std;

/** ffmpeg编译完成后支持的解封装器位于libavformat目录下的demuxer_list.c文件中,具体的配置再.configure文件中,如下:
 *  print_enabled_components libavformat/demuxer_list.c AVInputFormat demuxer_list $DEMUXER_LIST
 *  xxxx.mp4对应的解封装器为ff_mov_demuxer
 */
class Demuxer
{
public:
    Demuxer();
    ~Demuxer();

    void doDemuxer();
};
#endif /* demuxer_hpp */

点击并拖拽以移动

实现文件

//
//  demuxer.cpp
//  video_encode_decode
//
//  Created by apple on 2020/3/24.
//  Copyright © 2020 apple. All rights reserved.
//

#include "demuxer.hpp"

struct buffer_data {
    uint8_t *ptr;
    uint8_t *ptr_start;
    size_t size; ///< buffer size
};

Demuxer::Demuxer()
{

}

Demuxer::~Demuxer()
{

}

/** 参考ffmpeg源码file.c的file_seek方法。
 *  1、该函数的作用有两个,第一个返回外部缓冲区的大小,类似于lstat()函数;第二个设置外部缓冲读取指针的位置(读取指针的位置
 *  相对于缓冲区首地址来说的),类似于lseek()函数
 *  2、AVSEEK_SIZE 代表返回外部缓冲区大小
 *  3、SEEK_CUR 代表将外部缓冲区读取指针从目前位置偏移offset
 *  4、SEEK_SET 代表将外部缓冲区读取指针设置到offset指定的偏移
 *  5、SEEK_END 代表将外部缓冲区读取指针设置到相对于尾部地址的偏移
 *  6、3/4/5情况时返回当前指针位置相对于缓冲区首地址的偏移。offset 的值可以为负数和0。
 */
static int64_t io_seek(void* opaque,int64_t offset,int whence)
{
    struct buffer_data *bd = (struct buffer_data*)opaque;
    if (whence == AVSEEK_SIZE) {
        return bd->size;
    }

    if (whence == SEEK_CUR) {
        bd->ptr += offset;
    } else if (whence == SEEK_SET) {
        bd->ptr = bd->ptr_start+offset;
    } else if (whence == SEEK_END) {
        bd->ptr = bd->ptr_start + bd->size + offset;
    }

    return (int64_t)(bd->ptr - bd->ptr_start);
}

/** 参考ffmpeg file.c的file_read()源码
 *  1、该函数的意思就是需要从外部读取指定大小buf_size的数据到指定的buf中;这里外部是一个内存缓存
 *  2、每次读取完数据后需要将读取指针后移
 *  3、如果外部数据读取完毕,则需要返回AVERROR_EOF错误
 *  4、读取成功,返回实际读取的字节数
 */
static int io_read(void *opaque, uint8_t *buf, int buf_size)
{
    static int total = 0;
    struct buffer_data *bd = (struct buffer_data *)opaque;
    buf_size = FFMIN(buf_size, (int)(bd->ptr_start+bd->size-bd->ptr));
    total += buf_size;
    if (buf_size <= 0)
        return AVERROR_EOF;
//    LOGD("ptr:%p size:%zu buf_size %d total %d\n", bd->ptr, bd->size,buf_size,total);

    /* copy internal buffer data to buf */
    memcpy(buf, bd->ptr, buf_size);
    bd->ptr  += buf_size;

    return buf_size;
}

void Demuxer::doDemuxer()
{
    string curFile(__FILE__);
    unsigned long pos = curFile.find("1-video_encode_decode");
    if (pos == string::npos) {
        LOGD("can not find file");
        return;
    }

    string recourDir = curFile.substr(0,pos)+"filesources/";
    // mdata标签在moov之前
    string srcPath = recourDir+"test_1280x720.MP4";
    // mdata标签在moov之后
//    string srcPath = "/Users/apple/Downloads/Screenrecorder-2020-03-31-16-36-12-749\(0\).mp4";

    AVFormatContext *inFmtCtx = NULL;
    int ret = 0;
#define Use_Custom_io   0
#if Use_Custom_io
    AVIOContext *ioCtx;
    uint8_t *io_ctx_buffer = NULL,*buffer = NULL;
    size_t io_ctx_buffer_size = 4096,buffer_size;
    buffer_data bd = {0};
    ret = av_file_map(srcPath.c_str(),&buffer,&buffer_size,0,NULL);
    if (ret < 0) {
        LOGD("av_file_map fail");
        return;
    }
    bd.ptr = buffer;
    bd.ptr_start = buffer;
    bd.size = buffer_size;

    inFmtCtx = avformat_alloc_context();
    if (inFmtCtx == NULL) {
        LOGD("avformat_alloc_context fail");
        return;
    }
    io_ctx_buffer = (uint8_t*)av_mallocz(io_ctx_buffer_size);
    /** 遇到问题:如果没有指定io_seek函数,对于MP4文件来说,如果mdata在moov标签的后面,采用自定义的AVIOContext时
     *  候avformat_find_stream_info() 返回"Could not findcodec parameters for stream 0 ........:
     *  unspecified pixel formatConsider increasing the value for the 'analyzeduration' and 'probesize' options的错误
     *  av_read_frame()返回Invalid data found when processing input的错误
     *  分析原因:在创建AVIOContext时没有指定seek函数
     *  解决方案:因为创建AVIOContext时没有指定io_seek函数并正确实现io_read()和io_seek()相关逻辑;参考如上io_seek和io_read函数
     */
    ioCtx = avio_alloc_context(io_ctx_buffer,(int)io_ctx_buffer_size,0,&bd,&io_read,NULL,&io_seek);
    if (ioCtx == NULL) {
        LOGD("avio_alloc_context fail");
        return;
    }
    inFmtCtx->pb = ioCtx;
#endif
    /** 遇到问题:avformat_open_input -1094995529
     *  原因分析:这里要打开的文件是MP4文件,而对应的MP4的解封装器没有编译到ffmpeg中,--enable-demuxer=mov打开重新编译即可。
     */
    /** 参数1:AVFormatContext 指针变量,可以用avformat_alloc_context()先初始化或者直接初始化为NULL
     *  参数2:文件名路径
     *  参数3:AVInputFormat,接封装器对象,传NULL,则根据文件名后缀猜测。非NULL,则由这个指定的AVInputFormat进行解封装
     *  参数4:解封装相关参数,传NULL用默认即可
     */
    ret = avformat_open_input(&inFmtCtx,srcPath.c_str(),NULL,NULL);
    if (ret < 0) {
        LOGD("avformat_open_input fail %d error:%s",ret,av_err2str(ret));
        return;
    }

    LOGD("ddd probesize %d analyzeduration %d",inFmtCtx->probesize,inFmtCtx->max_analyze_duration);
    ret = avformat_find_stream_info(inFmtCtx,NULL);
    if (ret < 0) {
        LOGD("avformat_find_stream_info fail %d error:%s",ret,av_err2str(ret));
        return;
    }
    LOGD("begin av_dump_format");
    av_dump_format(inFmtCtx,0,NULL,0);
    LOGD("end av_dump_format");

    // 解析出封装格式中的标签
    LOGD("begin mediadata \n\n");
    const AVDictionaryEntry *tag = NULL;
    while ((tag = av_dict_get(inFmtCtx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
        LOGD("tag key:%s value:%s",tag->key,tag->value);
    }
    LOGD("end mediadata \n\n");

    // 解析出封装格式中的编码相关参数
    for (int i = 0;i<inFmtCtx->nb_streams;i++) {
        AVStream *stream = inFmtCtx->streams[i];
        enum AVCodecID cId = stream->codecpar->codec_id;
        int format = stream->codecpar->format;
        if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            LOGD("begin video AVStream \n\n");
            LOGD("code_id %s format ",avcodec_get_name(cId));

            const AVPixFmtDescriptor *fmtDes = av_pix_fmt_desc_get((enum AVPixelFormat)format);
            LOGD("AVPixFmtDescriptor name %s",fmtDes->name);

            LOGD("width %d height %d",stream->codecpar->width,stream->codecpar->height);
            /** 颜色的取值范围
             *  AVCOL_RANGE_MPEG:代表以BT601,BT709,BT2020等以广播电视系统类的颜色取值范围类型
             *  AVCOL_RANGE_JPEG:代表以电脑等显示器类的颜色取值范围
             */
            LOGD("color_range %d",stream->codecpar->color_range);
            /** 所采用的颜色空间,比如RGB,YUV,CMYK等等
             */
            LOGD("color_space %s",av_get_colorspace_name(stream->codecpar->color_space));
            LOGD("video_delay %d\n\n",stream->codecpar->video_delay);
        } else if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            LOGD("begin audio AVStream \n\n");
            LOGD("code_id %s format ",avcodec_get_name(cId));
            LOGD("samplefmt %s",av_get_sample_fmt_name((enum AVSampleFormat)format));
            LOGD("channel_layout %s channels %d",av_get_channel_name(stream->codecpar->channel_layout),stream->codecpar->channels);
            LOGD("sample_rate %d",stream->codecpar->sample_rate);
            LOGD("frame_size %d",stream->codecpar->frame_size);

        } else {
            LOGD("other type");
        }
    }
    LOGD("end AVStream\n\n");

    AVPacket *packet = av_packet_alloc();
    static int num = 0;
    LOGD("begin av_read_frame");
    while ((ret = av_read_frame(inFmtCtx, packet)) >= 0) {
        num++;
//        LOGD("av_read_frame size %d num %d",packet->size,num);

        av_packet_unref(packet);
    }
    LOGD("end av_read_frame ret %s",av_err2str(ret));

    /** 释放内存
     */
    avformat_close_input(&inFmtCtx);
    // 对于自定义的AVIOContext,先释放里面的buffer,在释放AVIOContext对象
#if Use_Custom_io
    if (ioCtx) {
        av_freep(&ioCtx->buffer);
    }
    avio_context_free(&ioCtx);
    av_file_unmap(buffer, buffer_size);
#endif
}

点击并拖拽以移动

备注:分别实现了自定义AVIOContext的方式和采用avformat_opent_input()函数默认方式进行解封装,Use_Custom_io为0代表采用默认方式

遇到问题

1、如果没有指定io_seek函数,对于MP4文件来说,如果mdata在moov标签的后面,采用自定义的AVIOContext时候avformat_find_stream_info() 返回"Could not findcodec parameters for stream 0 ........:unspecified pixel formatConsider increasing the value for the 'analyzeduration' and 'probesize' options的错误av_read_frame()返回Invalid data found when processing input的错误
分析原因:在创建AVIOContext时没有指定seek函数
解决方案:因为创建AVIOContext时没有指定io_seek函数并正确实现io_read()和io_see()相关逻辑;参考如上io_seek和io_read函数

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 96 4 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
这把我C
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区