为了账号安全,请及时绑定邮箱和手机立即绑定

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

标签:
ffmpeg

都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)

https://img1.sycdn.imooc.com//5de7c88b0001810612800610.jpg

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

https://img1.sycdn.imooc.com//5de7c88c00019ec912800359.jpg
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.过时的几个方法与替换说明

下图是新旧的示意图:

https://img1.sycdn.imooc.com//5de7c88c00015c4d12800870.jpg

注册方法,去了吧

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.几个主要的结构体
https://img1.sycdn.imooc.com//5de7c88d0001aea312800303.jpg

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

https://img1.sycdn.imooc.com//5de7c88d0001638c12800654.jpg

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

https://img1.sycdn.imooc.com//5de7c88d0001cb2112800695.jpg

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

https://img1.sycdn.imooc.com//5de7c92f0001fb7c06790356.jpg


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

https://img1.sycdn.imooc.com//5de7c88d0001387e12800530.jpg

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

https://img1.sycdn.imooc.com//5de7c88e0001f4c012800510.jpg

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

https://img1.sycdn.imooc.com//5de7c88e0001c6eb12800441.jpg




点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
移动开发工程师
手记
粉丝
21
获赞与收藏
57

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消