阅读 647

[- 贰 FFmpeg4.2.1 -] 解码decode-提取视频数据h264和YUV

FFmpeg4.2.1 系列

都0202年了,本文基于FFmpeg4.2.1,将使用最新版的api。让av_register_all()见鬼去吧!
FFmpeg的文章绝大多数都是3.X的,很多方法都过时了。对于喜新厌旧的洁癖君而言,满屏飘黄的警告、运行一下全是过时的警告是多么糟心。本文根据源码中的exsample进行改编,删繁就简,对于判空,校验返回值,打印错误什么的,自己在使用时注意一下,自行处理。


1. 讲个小故事

为了让你明白这篇文章是在干嘛,讲个小故事先:

捷特有两个护体神兽:白皇和黑皇    
白皇善鸣,声震天地。身长千尺,振翅遮天蔽日。  
黑皇善视,目之所见录于脑中,万世不灭。身高万丈,举手可握云擎天

像这种巨无霸级别的神兽是无法随身携带的。男主不被打到锁血,是不会召唤出来的。
那如何凸显主角的特别:动漫里的桥段是萌化成两个黑白服饰的漂亮妹子守护男主。
但两个人在面前晃晃悠悠也不好带回家,将二者封印在一个挂坠中,使用时进行召唤。

打BOSS怎么打的: 
捷特握着挂坠-->召唤黑白萌皇-->萌皇巨大化 --> 打Boss
复制代码

上面的故事包含音视频的数据概念:

非常大的原始数据: 音频pcm --> 巨兽白皇
编码后较小数据: 音频aac --> 人型萌白皇

非常大的原始数据: 视频YUV --> 巨兽黑皇
编码后较小数据: 视频h264 --> 人型萌黑皇

 mp4、ts、avi等封装体格式: aac + h264 ---->封印挂坠
 
 播放器怎么播放的: 
拆封封装格式-->召唤寻找编码流-->解码流 --> 渲染呈现
复制代码

这篇的目标是将挂坠(sh.ts)中的黑萌娘(sh.h264)召唤出来,并且转换成神兽黑皇(sh_768x432.yuv)

为什么说YUV是巨兽,看下面的数据就知道了。视频3分30秒,YUV数据飙到2.48G。也许你会觉得为什么会有YUV这样逆天的存在,其实渲染层需要YUV,给它压缩后的数据人家不认识。播放时会先进行解码, 你能在手机里存几百部片,都要感谢压缩格式。


2. 最精简代码

这里介绍主要的流程,这大白话的注释你要再看不懂我也没办法了。

#include <iostream>

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
};
using namespace std;

int main() {
    const char *rec_path = "/Volumes/coder/Projects/Media/TolyFF/res/sh.ts";
    AVFormatContext *fmt_ctx;//格式化上下文--召唤场地
    fmt_ctx = avformat_alloc_context();// 准备召唤场地

    avformat_open_input(&fmt_ctx, rec_path, nullptr, nullptr);//打开封印

    avformat_find_stream_info(fmt_ctx, nullptr);//找到召唤物

    int v_idx = av_find_best_stream(//寻找视频流索引 -- 萌黑
            fmt_ctx, AVMEDIA_TYPE_VIDEO,-1, -1, nullptr, 0);

    AVCodecParameters *c_par;//声明-召唤器参数
    AVCodecContext *cc_ctx;//声明-召唤器环境
    const AVCodec *codec;//声明-召唤器

    c_par = fmt_ctx->streams[v_idx]->codecpar;//实例化-召唤器参数
    codec = avcodec_find_decoder(c_par->codec_id);//实例化-召唤器

    //用参数c_par实例化编解码器上下文,,并打开编解码器
    cc_ctx = avcodec_alloc_context3(codec);//实例化-召唤器环境
    avcodec_parameters_to_context(cc_ctx, c_par);//召唤器环境参数填充
    avcodec_open2(cc_ctx, codec, nullptr);//打开召唤器

    AVPacket *pkt; //声明 萌黑-数据包
    AVFrame *frame;//声明 黑皇-帧
    
    pkt = av_packet_alloc();//准备 黑皇-帧
    frame = av_frame_alloc();//准备 萌黑-数据包

    FILE *dst_f=fopen("/Volumes/coder/Projects/Media/TolyFF/res/sh.h264","wb+");//萌黑实体sh.h264
    FILE *dst_f_yuv=fopen("/Volumes/coder/Projects/Media/TolyFF/res/sh_768x432.yuv","wb+");//黑皇实体sh_768x432.yuv

    while (av_read_frame(fmt_ctx, pkt) >= 0) {//持续读帧
        if (pkt->stream_index == v_idx) {
            avcodec_send_packet(cc_ctx, pkt);//发送 萌黑-数据包
            fwrite(pkt->data,1,pkt->size,dst_f);// 萌黑-数据包拼合集结 
            
            avcodec_receive_frame(cc_ctx, frame);//接收解码 -- 巨大化
            fwrite(frame->data[0],1,cc_ctx->width*cc_ctx->height,dst_f_yuv);// 黑皇Y-数据包拼合集结
            fwrite(frame->data[1],1,cc_ctx->width/2*cc_ctx->height/2,dst_f_yuv);// 黑皇U-数据包拼合集结
            fwrite(frame->data[2],1,cc_ctx->width/2*cc_ctx->height/2,dst_f_yuv);// 黑皇V-数据包拼合集结
        }
    }
    fclose(dst_f);// 萌黑OK , 拔掉连接头
    fclose(dst_f_yuv);// 萌黑OK , 拔掉连接头
    avcodec_free_context(&cc_ctx);//关闭环境
    av_frame_free(&frame);//释放本体
    av_packet_free(&pkt);//释放载体
}
复制代码

运行后会生成h264和YUV两个文件,通过ffplay可以播放
两者分别是纯视频的压缩流和原始流,所以播放起来是没有声音的。
你也许会问为嘛要解码出这两个流,有一种神技叫做融合,有一种操作叫做变换。少年呦,你对于力量一无所知。

ffplay sh.h264
ffplay -video_size 768x432 sh_768x432.yuv
复制代码

2.过时的几个方法与替换说明

下图是新旧的示意图:

注册方法,去了吧

attribute_deprecated
void av_register_all(void);
复制代码

AVStream中的codec方法可以获取AVCodecContext,已过时。
取而代之的是codecpar的AVCodecParameters,在根据参数去创建上下文。如上

cc_ctx = fmt_ctx->streams[v_idx]->codec;

    /**
     * @deprecated use the codecpar struct instead
     */
    attribute_deprecated
    AVCodecContext *codec;
复制代码

avcodec_decode_video2 方法被拆成了两个:avcodec_send_packet()avcodec_receive_frame()分别用于包和帧的处理。透露一下packet用来召唤萌娘,frame用来激活巨兽。

 * @deprecated Use avcodec_send_packet() and avcodec_receive_frame().
 */
attribute_deprecated
int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
                         int *got_picture_ptr,
                         const AVPacket *avpkt);
复制代码

3.几个主要的结构体

AVFormatContext:
封装格式上下文,包含一些介绍信息,及最重要的码流stream,这就是数据的源泉。AVInputFormat记录了封装格式的信息。


AVCodecParameters:
编解码器的参数,从AVStream的codecpar获取,取代codec属性。可以获取流的参数,如视频流宽、高、编解码器类型、编解码器id等。


AVCodecContext:
编解码器的上下文,可通过avcodec_parameters_to_context使用AVCodecParameters进行参数填充。也是记录着视频流的信息,不同的是他包含了编解码器对象codec。


AVCodec:
编解码器的上下文,相当于大古的神光棒,能让大古变成光的东西。


AVPacket: 编码后的码流,对应现视频,data字段也就是以及压缩后的h264数据。初次之外还有其他信息:


AVFrame:
解码后的原始流,YUV分量合并可形成巨大的YUV神兽。


@张风捷特烈 2019.11.26 未允禁转
我的公众号:编程之王
~ END ~

关注下面的标签,发现更多相似文章
评论