都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神兽。
共同学习,写下你的评论
评论加载中...
作者其他优质文章