ffmpeg视频编解码 demo初探(一)(包含下载指定windows版本ffmpeg)分离视频文件中的视频流每一帧YUV图片
创始人
2024-01-22 11:30:54

参考文章1:YUV数据流编码成H264

参考文章2:【FFmpeg编码实战】(1)将YUV420P图片集编码成H264视频文件

文章目录

    • 第一个项目:分离视频文件中的视频流每一张图片
      • 弯路
      • 步入正轨
        • 下载官方编译的ffmpeg4.3(win64-gpl-shared-4.3.zip)
        • 拷贝ffmpeg文件到项目
        • 配置vs
        • 运行项目
        • 查看文件

第一个项目:分离视频文件中的视频流每一张图片

先参考这个文章:【FFmpeg解码实战】(2)分离视频文件中的视频流每一张图片(进阶)(C)

这篇文章的上一篇文章:【FFmpeg解码实战】(1)解码并分离视频文件中的音频流和视频流(C),也是第一篇demp,我们跳过

博主提供了工程下载方式:VS2019-解码视频-工程所有文件.zip

这个下载的项目其实是把解码并分离视频文件中的音频流和视频流分离视频文件中的视频流每一张图片合在一起了,通过宏开关去控制
在这里插入图片描述

弯路

里面没有ffmpeg,我们下载博主编译好的或者直接到官网下载

博主给的:

你要ARM 的话,可以使用这个: https://download.csdn.net/download/Ciellee/13027058
windows 的话,可以用这个 : https://download.csdn.net/download/Ciellee/12915075
都是我之前用最新的 4.3 源码自已编译的

我没有用博主的,直接找方法自己编译了一个,不过是4.4版本的,不知道兼容不兼容
Visual Studio 2019 VS编译ffmpeg4.4为静态库或动态库

然后我们在博主的工程里面,添加include和lib两个目录,并且把需要的头文件和库文件拷贝进去

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在vs项目种配置附加包含目录和附加库目录

看vs项目的这个地方发现博主是这样包含ffmpeg的头文件和库目录的,我们把它改了
在这里插入图片描述
在这里插入图片描述

改成这样
在这里插入图片描述
在这里插入图片描述
然后运行项目,发现报错了,错误 C4996 'av_init_packet': 被声明为已否决,应该这个函数不推荐使用了,我们看下4.4有没替代接口
在这里插入图片描述

在ffmpeg编译工程里查找,发现这个文档,参考文章:ffmpeg的API函数变化记录

在这里插入图片描述

看了下,貌似没替代接口,那么我们只能“沉默”它了
在这里插入图片描述
在代码开头添加#pragma warning(disable : 4996)
在这里插入图片描述
运行报错:
在这里插入图片描述
按博主提供的ffmpeg4.3配置了,也还是同样问题

严重性 代码 说明 项目 文件 行 禁止显示状态
警告 MSB8028 中间目录(x64\Debug)包含从另一个项目(1-解码视频.vcxproj)共享的文件。
这会导致错误的清除和重新生成行为。 demux_video C:\Program Files (x86)\Microsoft Visual
Studio\2019\Community\MSBuild\Microsoft\VC\v160\Microsoft.CppBuild.targets 513

严重性 代码 说明 项目 文件 行 禁止显示状态
警告 LNK4078 找到多个“.rdata”节,它们具有不同的特性(C0400040) demux_video F:\ArnoldCppTest\20221113_ffmpeg_test\prj\libavformatd.lib(invert_limb_table.obj) 1

步入正轨

下载官方编译的ffmpeg4.3(win64-gpl-shared-4.3.zip)

遇到很多困难啊,后来发现是博主给的ffmpeg有问题,后来我到ffmpeg官网,图中箭头所指的地方下到了4.3老版本

在这里插入图片描述

我一直往后面翻,终于找到了4.3老版本,

https://github.com/BtbN/FFmpeg-Builds/releases

然后下载了

https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2021-03-31-12-39/ffmpeg-n4.3.2-162-g4bbcaf7559-win64-gpl-shared-4.3.zip

在这里插入图片描述
然后下下来解压,把里面的一些东西拷贝到我们项目目录中,主要就是头文件,静态库文件,动态库文件,其中动态库文件注意要放在项目根目录

拷贝ffmpeg文件到项目

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

配置vs

因为下下来的没见带d,所以估计是release版本的,我们在Release x64下配置

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

因为我们是在代码中引用附加依赖项的,所以不用在vs里填了

在这里插入图片描述

运行项目

把博主那个视频文件拷贝到项目根目录下

在这里插入图片描述
发现有的代码不大对,我们改一下,
main.cpp

/*ffmpeg lib 测试程序*/#include 
#include 
#include 
#include 
#include 
#include extern "C"
{#include #include #include #include 		//av_ts2timestr#include 
}#define YUV420P_FILE	1	// 视频流保存成 yuv420p 图片
//#define H264_FILE	    1	// 视频流保存成 H264 文件#ifdef _DEBUG#pragma comment(lib, "libavformatd.lib")#pragma comment(lib, "libavutild.lib")
#else//#pragma comment(lib, "libavformat.lib")#pragma comment(lib, "avformat.lib")//#pragma comment(lib, "libavutil.lib")#pragma comment(lib, "avutil.lib")#pragma comment(lib, "avcodec.lib")#pragma comment(lib, "avfilter.lib")#pragma comment(lib, "swscale.lib")#pragma comment(lib, "swresample.lib")#pragma comment(lib, "postproc.lib")#pragma comment(lib, "avdevice.lib")
#endifusing namespace std;static int get_format_from_sample_fmt(const char** fmt, enum AVSampleFormat sample_fmt)
{int i;struct sample_fmt_entry {enum AVSampleFormat sample_fmt; const char* fmt_be, * fmt_le;} sample_fmt_entries[] = {{ AV_SAMPLE_FMT_U8,  "u8",    "u8"    },{ AV_SAMPLE_FMT_S16, "s16be", "s16le" },{ AV_SAMPLE_FMT_S32, "s32be", "s32le" },{ AV_SAMPLE_FMT_FLT, "f32be", "f32le" },{ AV_SAMPLE_FMT_DBL, "f64be", "f64le" },};*fmt = NULL;for (i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entries); i++) {struct sample_fmt_entry* entry = &sample_fmt_entries[i];if (sample_fmt == entry->sample_fmt) {*fmt = AV_NE(entry->fmt_be, entry->fmt_le);return 0;}}fprintf(stderr,"sample format %s is not supported as output format\n",av_get_sample_fmt_name(sample_fmt));return -1;
}// 参考:ffplay.c、demuxing_decoding.c
int main(int argc, char* argv[])
{int ret = 0;//printf("%s \n",avcodec_configuration());// 定义文件名const char input_filename[] = "video.mp4";const char out_filename[] = "video_out";char video_dst_filename[50]; // = "Video_Test_out.h264";char audio_dst_filename[50]; // = "Video_Test_out.aac";memset(video_dst_filename, '\0', 50);memset(audio_dst_filename, '\0', 50);// 1. 打开文件,分配AVFormatContext 结构体上下文AVFormatContext* fmt_ctx = NULL;	// 定义音视频格式上下文结构体if (avformat_open_input(&fmt_ctx, input_filename, NULL, NULL) < 0) {printf("Could not open source file %s\n", input_filename);return 0;}// 2. 查找文件对应的流信息if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {printf("Could not find stream information\n");return 0;}// 3. 打印流信息av_dump_format(fmt_ctx, 0, input_filename, 0);// 4. 视频解码器初始化// 4.1 获取视频对应的stream_indexint video_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);printf("\n#===> Find video_stream_idx = %d\n", video_stream_idx);// 4.2 获取到stream 数据AVStream* video_st = fmt_ctx->streams[video_stream_idx];// 4.3 根据 codec_id 查找解码器AVCodec* video_dec = avcodec_find_decoder(video_st->codecpar->codec_id);// 4.4 初始化解码器上下文信息AVCodecContext* video_dec_ctx = avcodec_alloc_context3(video_dec);// 4.5 复制 codec 相关参数到解码器上下文中avcodec_parameters_to_context(video_dec_ctx, video_st->codecpar);printf("\n#===> Find decoder: %s,  coded_id:%d  long name: %s  pix_fmt=%d (%s)\n", video_dec->name, video_dec->id, video_dec->long_name, video_dec_ctx->pix_fmt, av_get_pix_fmt_name(video_dec_ctx->pix_fmt));// 4.6 初始化并打开解码器AVDictionary* video_opts = NULL;avcodec_open2(video_dec_ctx, video_dec, &video_opts);// 5. 音频解码器初始化// 5.1  获取音频对应的stream_indexint audio_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);printf("\n#===> Find audio_stream_idx = %d\n", audio_stream_idx);// 5.2 获取到stream 数据AVStream* audio_st = fmt_ctx->streams[audio_stream_idx];// 5.3 根据 codec_id 查找解码器AVCodec* audio_dec = avcodec_find_decoder(audio_st->codecpar->codec_id);printf("\n#===> Find decoder: %s,  coded_id:%d  long name: %s\n", audio_dec->name, audio_dec->id, audio_dec->long_name);// 5.4 初始化解码器上下文信息AVCodecContext* audio_dec_ctx = avcodec_alloc_context3(audio_dec);// 5.5 复制 codec 相关参数到解码器上下文中avcodec_parameters_to_context(audio_dec_ctx, audio_st->codecpar);// 5.6 初化并打开音频解码器AVDictionary* audio_opts = NULL;avcodec_open2(audio_dec_ctx, audio_dec, &audio_opts);// 6. 配置视频解复用后要保存的位置FILE* video_dst_file = NULL, * audio_dst_file = NULL;#ifdef H264_FILEsprintf_s(video_dst_filename, 50, "%s.%s", out_filename, video_dec->name);ret = fopen_s(&video_dst_file, video_dst_filename, "wb");printf("open file:%s   ret:%d\n", video_dst_filename, ret);
#endif#ifdef YUV420P_FILE//文件夹名称char folderName[] = "video";// 文件夹不存在则创建文件夹if (_access(folderName, 0) == -1){_mkdir(folderName);}snprintf(audio_dst_filename, 50, "video/%s.%s", out_filename, audio_dec->name);
#elsesprintf_s(audio_dst_filename, 50, "%s.%s", out_filename, audio_dec->name);
#endifret = fopen_s(&audio_dst_file, audio_dst_filename, "wb");printf("open file:%s   ret:%d\n", audio_dst_filename, ret);uint8_t* video_dst_data[4] = { NULL };int video_dst_linesize[4] = { 0 };// 7. 计算视频数据大小size_t video_dst_bufsize = av_image_alloc(video_dst_data, video_dst_linesize,video_dec_ctx->width, video_dec_ctx->height, video_dec_ctx->pix_fmt, 1);// 8. 分配并初始化 AVFrame、AVPacketAVFrame* frame = av_frame_alloc();AVPacket pkt;av_init_packet(&pkt);pkt.data = NULL;pkt.size = 0;int video_frame_count = 0, audio_frame_count = 0;printf("Start read frame\n");//  9. 循环读取 一帧数据while (av_read_frame(fmt_ctx, &pkt) >= 0) {// 10. 视频数据解码if (pkt.stream_index == video_stream_idx){// 10.1 将 packet 数据 发送给解码器ret = avcodec_send_packet(video_dec_ctx, &pkt);// 10.2 获取解码后的帧数据while (ret >= 0) {ret = avcodec_receive_frame(video_dec_ctx, frame);if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)){ret = 0;break;}// 10.3 保存帧数据到视频文件中//printf("#===> video_frame n:%d  coded_n:%d  ", video_frame_count++, frame->coded_picture_number);av_image_copy(video_dst_data, video_dst_linesize,(const uint8_t**)(frame->data), frame->linesize, video_dec_ctx->pix_fmt, video_dec_ctx->width, video_dec_ctx->height);
#ifdef YUV420P_FILE// 10.4 写入文件sprintf_s(video_dst_filename, 50, "video/%s.%s.%d.yuv", out_filename, av_get_pix_fmt_name(video_dec_ctx->pix_fmt), video_frame_count++);ret = fopen_s(&video_dst_file, video_dst_filename, "wb");//printf("open file:%s   ret:%d\n", video_dst_filename, ret);
#endifret = (int)fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file);//printf("Write Size:%d\n", ret);
#ifdef YUV420P_FILEfclose(video_dst_file);
#endif}}// 11. 音频数据解码  else if (pkt.stream_index == audio_stream_idx){// 11.1 将 packet 数据 发送给解码器ret = avcodec_send_packet(audio_dec_ctx, &pkt);// 11.2 获取解码后的帧数据while (ret >= 0) {ret = avcodec_receive_frame(audio_dec_ctx, frame);if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)){ret = 0;break;}// 11.3 写入文件//printf("#===> audio_frame n:%d  nb_samples:%d  pts:%s ",//		audio_frame_count++, frame->nb_samples, av_ts2timestr(frame->pts, &audio_dec_ctx->time_base));ret = (int)fwrite(frame->extended_data[0], 1, frame->nb_samples * av_get_bytes_per_sample((AVSampleFormat)(frame->format)), audio_dst_file);//printf("Write Size:%d\n", ret);}}av_frame_unref(frame);// 清空AVPacket结构体数据av_packet_unref(&pkt);if (ret < 0)break;}// 12.发送一个空包,刷新解码器ret = avcodec_send_packet(video_dec_ctx, NULL);// 12.1 获取解码后的帧数据while (ret >= 0) {ret = avcodec_receive_frame(video_dec_ctx, frame);if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)){printf("break, video ret=%d \n", ret);ret = 0;break;}}ret = avcodec_send_packet(audio_dec_ctx, NULL);// 12.2 获取解码后的帧数据while (ret >= 0) {ret = avcodec_receive_frame(audio_dec_ctx, frame);if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)){printf("break, audio ret=%d \n", ret);ret = 0;break;}}// 13. 解复用完毕printf("Demuxing succeeded.\n");printf("Play the output video file with the command:\n""ffplay -f rawvideo -pix_fmt %s -video_size %dx%d %s\n",av_get_pix_fmt_name(video_dec_ctx->pix_fmt), video_dec_ctx->width, video_dec_ctx->height, video_dst_filename);FILE* bat_dst_file = NULL;char bat_out[100] = "";memset(bat_out, '\0', 100);#ifdef YUV420P_FILEmemset(video_dst_filename, '\0', 50);sprintf_s(video_dst_filename, 50, "%s.%s.%d.yuv", out_filename, av_get_pix_fmt_name(video_dec_ctx->pix_fmt), 0);#endifsprintf_s(bat_out, 99, "ffplay -f rawvideo -pix_fmt %s -video_size %dx%d %s",av_get_pix_fmt_name(video_dec_ctx->pix_fmt), video_dec_ctx->width, video_dec_ctx->height, video_dst_filename);#ifdef YUV420P_FILEret = fopen_s(&bat_dst_file, "video/play_video.bat", "wb");
#elseret = fopen_s(&bat_dst_file, "play_video.bat", "wb");
#endifret = (int)fwrite(bat_out, 1, sizeof(bat_out), bat_dst_file);fclose(bat_dst_file);enum AVSampleFormat sfmt = audio_dec_ctx->sample_fmt;int n_channels = audio_dec_ctx->channels;const char* fmt;if (av_sample_fmt_is_planar(sfmt)) {const char* packed = av_get_sample_fmt_name(sfmt);printf("Warning: the sample format the decoder produced is planar ""(%s). This example will output the first channel only.\n",packed ? packed : "?");sfmt = av_get_packed_sample_fmt(sfmt);n_channels = 1;}if ((ret = get_format_from_sample_fmt(&fmt, sfmt)) < 0)goto end;printf("Play the output audio file with the command:\n""ffplay -f %s -ac %d -ar %d %s\n",fmt, n_channels, audio_dec_ctx->sample_rate,audio_dst_filename);memset(bat_out, '\0', 100);
#ifdef YUV420P_FILEmemset(audio_dst_filename, '\0', 50);sprintf_s(audio_dst_filename, 50, "%s.%s", out_filename, audio_dec->name);
#endifsprintf_s(bat_out, 99, "ffplay -f %s -ac %d -ar %d %s",fmt, n_channels, audio_dec_ctx->sample_rate,audio_dst_filename);
#ifdef YUV420P_FILEret = fopen_s(&video_dst_file, "video/play_audio.bat", "wb");
#elseret = fopen_s(&video_dst_file, "play_audio.bat", "wb");
#endifret = (int)fwrite(bat_out, 1, sizeof(bat_out), bat_dst_file);fclose(video_dst_file);end:avcodec_free_context(&video_dec_ctx);avcodec_free_context(&audio_dec_ctx);avformat_close_input(&fmt_ctx);#ifdef H264_FILEfclose(video_dst_file);
#endiffclose(audio_dst_file);av_frame_free(&frame);av_free(video_dst_data[0]);return 0;
}

然后vs上运行项目(注意重复运行项目时,不要打开里面生成的文件,以免占用)

在这里插入图片描述

查看文件

然后到项目根目录查看,发现多了个video目录,里面有一个.aac文件和很多YUV文件,除此之外,还有俩.bat脚本文件,

play_audio.bat:
ffplay -f f32le -ac 1 -ar 44100 video_out.aac

play_video.bat
ffplay -f rawvideo -pix_fmt yuv420p -video_size 1050x540 video_out.yuv420p.0.yuv

我们把ffmpeg可执行文件和动态库文件拷贝进来,然后执行那俩脚本文件,就能播放.aac文件和YUV文件了,不过貌似是播放视频的,我们生成的单帧文件,所以就只能播放出第一帧

在这里插入图片描述

双击play_audio.bat能播放.aac文件:

在这里插入图片描述

双击play_video.bat能播放查看YUV第一帧:

在这里插入图片描述

用YUView也能查看文件,不过要把图片正确的宽高和编码方式写对,宽1050,高540

在这里插入图片描述

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
ps局部放大 放大某个部位的图... 软件类学习建议:边看边练习,可以先收藏后学习。一、使用Ctrl快捷键放大:Ctrl + +缩小:Ct...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
ps局部放大 放大某个部位的图... 软件类学习建议:边看边练习,可以先收藏后学习。一、使用Ctrl快捷键放大:Ctrl + +缩小:Ct...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...