FFmpeg音视频解码

libavcodec:主要实现一系列编码器的实现

一编码/解码常见结构体

(1) AVCodec:编码器信息
(2) AVCodecContext:编码器上下文

(3)AVFrame:音视频的原始帧数据,对应的AVPacket里面的帧是压缩后的帧.

2 内存分配和释放

av_frame_alloc()\av_frame_free() 生成和释放:AVFrame

  // 分配编解码器的上下文
avcodec_alloc_context3()/avcodec_free_context 

二 解码

avcodec_find_decoder:查找解码器

   AVCodec *codec;
  // 获取解码器实例, st 是AVStream
   codec = avcodec_find_decoder(st->codecpar->codec_id)(旧版本)
// codec = avcodec_find_decoder(AV_CODEC_ID_MPEG1VIDEO)(新版本)
// 新版本
AVCodecParameters *codec_param = ctx->streams[ret]->codecpar;
const AVCodec *codec = avcodec_find_decoder(codec_param->codec_id);

avcodec_open2:打开解码器
avcodec_decode_video2:视频解码(已废弃).

注意在新版本中如ffmpeg4.3.1中视频解码使用avcodec_send_packetavcodec_receive_frame 实现了后台解码,源码如下:

 av_log_set_level(AV_LOG_INFO);
    AVFormatContext *ctx = NULL;
    ctx = avformat_alloc_context();
    // 打开文件
    int ret = avformat_open_input(&ctx, "./Desktop/ffmpeg/基础一/打开本地文件和网络文件/scale.MP4", NULL, NULL);
    if (ret != 0)
    {
        av_log(NULL, AV_LOG_ERROR, "视频文件打开失败\n");
        goto __fail;
    }
    if (avformat_find_stream_info(ctx, NULL) < 0)
    {
        /* code */
        av_log(NULL, AV_LOG_ERROR, "获取视频流失败\n");
        goto __fail;
    }
    AVPacket *pkt = NULL;
    pkt = av_packet_alloc();
    // streams 数量
    int streamCount = ctx->nb_streams;
    // 获取所有的AVStream 一般是2路 音频和视频 [0]音频 [1]视频
    for (int i = 0; i < streamCount; i++)
    {
        // 获取streams
        AVStream *stream = ctx->streams[i];
        //  index
        int stramIndex = stream->index;
        av_log(NULL, AV_LOG_ERROR, "stramIndex=%d\n", stramIndex);
    }
    // 获取streams 取第一个进行解码
    AVStream *stream = ctx->streams[1];
    //  index
    int stramIndex = stream->index;
    av_log(NULL, AV_LOG_ERROR, "codec_type=%d\n", stream->codecpar->codec_type);
    AVCodecContext *codec_content = NULL;
    codec_content = avcodec_alloc_context3(NULL);
    AVCodecParameters *codecpar = NULL;
    codecpar = avcodec_parameters_alloc();
    // 拷贝一份AVCodecParameters
    avcodec_parameters_copy(codecpar, stream->codecpar);
    avcodec_parameters_to_context(codec_content, codecpar);
    const AVCodec *avCodec = avcodec_find_decoder(codec_content->codec_id);
    int isopen = avcodec_open2(codec_content, avCodec, NULL);
    if (isopen)
    {
        av_log(NULL, AV_LOG_ERROR, "avcodec_open2解码器打开失败\n");
        goto __fail;
    }

    // getStream(ctx, fdsfds);

    while (av_read_frame(ctx, pkt) >= 0)
    {
        // av_log(NULL, AV_LOG_INFO, "读取视频流成功\n");
        // int send = avcodec_send_packet(codec_content, pkt);
        // 在解码前 必须知道是哪路流
        if (pkt->stream_index == stramIndex)
        {
            int send = avcodec_send_packet(codec_content, pkt);
            if (send)
            {
                av_log(NULL, AV_LOG_ERROR, "avcodec_send_packet失败\n");
                continue;
            }

            while (1)
            {
                AVFrame *av_frame = NULL;
                av_frame = av_frame_alloc();
                if (avcodec_receive_frame(codec_content, av_frame))
                {
                    break;
                }
               // 视频解码后的数据
                int width = av_frame->width;
                int height = av_frame->height;
                enum AVPixelFormat format = (enum AVPixelFormat)av_frame->format;
                char *str = (char *) malloc(128);
                str =  av_get_pix_fmt_string(str,128,format);
                av_log(NULL, AV_LOG_ERROR, "width = %d,height = %d  pix_fmt= %s\n", width, height,str);
                av_log(NULL, AV_LOG_ERROR, "解码成功!!!!\n");
                free(str);
            }
        }
    }
    // 可能还有缓存帧 需要把缓存解码 不然会有丢失原来视频帧的情况
    int last_send = avcodec_send_packet(codec_content, NULL);
    if (last_send)
    {
        av_log(NULL, AV_LOG_ERROR, "avcodec_send_packet缓存帧失败或者没有\n");
        goto __fail;
    }

    while (1)
    {
        AVFrame *av_frame = NULL;
        av_frame = av_frame_alloc();
        av_log(NULL, AV_LOG_ERROR, "%d\n",avcodec_receive_frame(codec_content, av_frame));
        if (avcodec_receive_frame(codec_content, av_frame))
        {
            break;
        }
        av_log(NULL, AV_LOG_ERROR, "缓存帧解码成功!!!!\n");
    }

__fail:
    avformat_close_input(&ctx);
    if (ctx)
    {
        avformat_free_context(ctx);
        ctx = NULL;
    }
    if (pkt)
    {
        av_packet_free(&pkt);
        pkt = NULL;
    }

    if (codec_content)
    {
        avcodec_free_context(&codec_content);
        codec_content = NULL;
    }

    if (codecpar)
    {
        avcodec_parameters_free(&codecpar);
        codecpar = NULL;
    }

三 编码

avcodec_find_encoder_by_name:查找编码器,和解码不同的是,通过名字查找.
avcodec_open2:编码的时候需要设置一些参数,分辨率,帧率等
avcodec_encode_video2:编码 编解码ffmpeg用的是第三方库比如lib264,ffmpeg只是二次封装

H264编码一些参数设置

  AVCodecContext * codec = NULL;
c = avcodec_alloc_context3(codec);
  //libx264
   // 查询编解码器
   codec = avcodec_find_encoder_by_name(codec_name);
   // 码率
   c->bit_rate = 400000;
  // 分辨率
  c->width = 352;
  c->height = 288;
 // 时间基 随帧率变化 1/25
   c->time_base = (AVRational){1, 25};
  //  帧率越大 码率越大 流畅度越好
   c->framerate = (AVRational){25, 1};
   // gop_size:多少帧产生一个关键帧,比如 120帧  c->gop_size = 10 120分成10组,10帧有个关键帧,后面都是参考帧  gop_size设置很小的话:码率增加  关键帧就多了 设置太大 码率减少,关键帧少了
   c->gop_size = 10;
   // 1 b帧:前后参考帧  max_b_frames 压缩率非常高
   // 2 p帧:向前参考帧 参考帧越多 ,压缩率越高 同样编码时长越大,实时性有影响
   // 3 i帧:关键帧
   c->max_b_frames = 1;
   // YUV 420
   c->pix_fmt = AV_PIX_FMT_YUV420P;

AAC 编码的一些设置参数设置

   AVCodecContext * codec = NULL;
c = avcodec_alloc_context3(codec);

 // 比特率64k
   c->bit_rate = 64000;
// 采样格式 16位的
    c->sample_fmt = AV_SAMPLE_FMT_S16;
// 采样率
    c->sample_rate    = select_sample_rate(codec);
 c->channel_layout = select_channel_layout(codec);
// 通道
c->channels       = av_get_channel_layout_nb_channels(c->channel_layout);

结语:在学习ffmpeg其他内容之前,调研一下SDL.主要为SDL安装,编译程序,窗口创建,SDL事件处理,纹理渲染等.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容