ffmpeg视频编解码 demo初探(包含下载指定windows版本ffmpeg)分离视频文件中的视频流每一帧YUV图片
Posted Dontla
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ffmpeg视频编解码 demo初探(包含下载指定windows版本ffmpeg)分离视频文件中的视频流每一帧YUV图片相关的知识,希望对你有一定的参考价值。
参考文章2:【FFmpeg编码实战】(1)将YUV420P图片集编码成H264视频文件
文章目录
第一个项目:分离视频文件中的视频流每一张图片
先参考这个文章:【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
然后下载了
然后下下来解压,把里面的一些东西拷贝到我们项目目录中,主要就是头文件,静态库文件,动态库文件,其中动态库文件注意要放在项目根目录
下
拷贝ffmpeg文件到项目
配置vs
因为下下来的没见带d,所以估计是release版本的,我们在Release x64下配置
因为我们是在代码中引用附加依赖项的,所以不用在vs里填了
运行项目
把博主那个视频文件拷贝到项目根目录下
发现有的代码不大对,我们改一下,
main.cpp
/*
ffmpeg lib 测试程序
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <iostream>
#include <io.h>
#include <direct.h>
extern "C"
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libavutil/timestamp.h> //av_ts2timestr
#include <libavutil/samplefmt.h>
#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")
#endif
using 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_index
int 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_index
int 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_FILE
sprintf_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);
#else
sprintf_s(audio_dst_filename, 50, "%s.%s", out_filename, audio_dec->name);
#endif
ret = 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、AVPacket
AVFrame* 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);
#endif
ret = (int)fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file);
//printf("Write Size:%d\\n", ret);
#ifdef YUV420P_FILE
fclose(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_FILE
memset(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);
#endif
sprintf_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_FILE
ret = fopen_s(&bat_dst_file, "video/play_video.bat", "wb");
#else
ret ffmpeg视频编解码 demo初探(包含下载指定windows版本ffmpeg)将YUV图片序列作为流读入,编码封装成x264 MP4视频
ffmpeg视频编解码 demo初探(包含下载指定windows版本ffmpeg)将YUV图片序列作为流读入,编码封装成x264 MP4视频