乐趣区

C-FFmpeg41版本东京热初体验

我的目的就是通过 FFmpeg 来对 h264 文件进行解码播放;

开始骚操作;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

第一步,打开 http://ffmpeg.org/

找到 Download


找到 Windows Builds,点击进入


需要下载两个版本(如果你编写的程序是 32 位程序就下载 32 位的,如果你程序是 64 位的就要下载 64 位的)

1、Shared(dll 文件)

2、Dev(包含了头文件、lib、示例代码)


下载完后,等待被使用;

第二步,新建 MFC 程序,然后运行一下(生成 debug 文件夹);

从刚刚下载的 FFmpeg-dev 里面拷贝 lib 和 include 文件夹到项目路径(我放在 cpp 文件的上一层目录,这个看你自己对项目架构的设计)


目目录的 debug 文件夹


一共有 8 个 dll 文件,注意目录;其实这个目录就是 exe 所在的目录;

第三步,配置 FFmpeg

1、配置 include

右键项目属性,配置属性 ->C/C++-> 常规 -> 附加包含目录 -> 键入..include(因为我的刚刚拷贝 include 目录的时候,我放在 cpp 文件的上一层,所以是.include)

2、配置 lib

右键项目属性,配置属性 -> 链接器 -> 常规 -> 附加库目录 ->..lib(因为我的刚刚拷贝 lib 目录的时候,我放在 cpp 文件的上一层,所以是.lib)

3、配置 lib 文件

右键项目属性,配置属性 -> 链接器 -> 输入 -> 附加依赖项 -> 输入所有 FFmpeg lib 目录下的 lib 文件名


打开 cpp 文件包含 FFmpeg 头文件

define __STDC_CONSTANT_MACROS

extern “C”{

include <libavcodec/avcodec.h>

include <libavformat/avformat.h>

include <libswscale/swscale.h>

include <libavutil/imgutils.h>

include <libswresample/swresample.h>

}

注 1:#define __STDC_CONSTANT_MACROS 这个需要定义在 include 的前面

注 2:因为已经在第三步第三项中输入了所有 lib,所以不需要通过 #pragma comment(lib,”xxx.lib”) 来链接。

然后编译运行测试一下,编译时有什么错误就百度解决一下;没有错误就最好了,祝顺利。

第四步,拖界面

因为之前看过雷神的教程,所以界面就按这个拖出来的;


File button 为选择文件的按钮,选着文件后,把路径在 Edit Control 控件中显示,可以自行百度如何实现;

第五步,实现代码

按照上面的界面,实现逻辑;

大概逻辑:选择文件路径 -> 点击播放 -> 解码文件 -> 在 Picture control 中显示;

重点我们放在视频解码

点击播放的响应事件

// 点击播放按钮响应时间

void CMFCFFmpeg41Player1Dlg::OnBnClickedButton2()

{

// TODO: 在此添加控件通知处理程序代码

if(mVideoPath.IsEmpty()){

MessageBox(TEXT(“ 请选择视频文件路径 ”));

return;

}

AfxBeginThread(Thread_Play,this);

}

mVideoPath 是 Edit Control 控件的 Value 关联对象;先判断是否为空,然后消息提示;如果不为空,开启线程进行解码, 并把 this 指针传递给了线程函数;

看 Thread_Play

注:Thread_Play 的定义要遵循特定的格式

UINT Thread_Play(LPVOID lpParam){

Play_H264_File(lpParam);

cout << “ 播放文件线程结束 ” << endl;

return 0;

}

在线程函数中调用了 Play_H264_File 函数;

重点就是 Play_H264_File 函数

我们看看:

AVFormatContext*pFormatCtx; // 解码上下文

AVCodecContext*pCodecCtx; // 解码器上下文

AVCodec*pCodec; // 加码器

AVFrame*pFrame; // 解码后的数据结构体

AVFrame*pFrameYUV; // 解码后的数据再处理的结构体

AVPacket*packet; // 解码前的数据结构体

uint8_t*out_buffer; // 数据缓存

int v_index; // 视频流的轨道下标

int v_size; // 一帧数据的大小

CMFCFFmpeg41Player1Dlg *dlg; //

int dely_time; // 需要暂停的毫秒数

/////////////////////// 先这么解释,结合代码再看 /////////////////////////////

// 先把刚刚传递进来的 this 指针,转换成可调用的对象

dlg = (CMFCFFmpeg41Player1Dlg *)lpParam;

// 获取指定的视频路径

char filepath[250]={0};

GetWindowTextA(dlg->mVideoEdit,(LPSTR)filepath,250); //mVideoEdit 是文件路径对象 Edit Control 控件关联的 Control 关联的对象

pFormatCtx = avformat_alloc_context(); // 获取解码上下文

// 解码上下文关联文件

if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){

cout << “ 视频文件打开失败 ” << endl;

return;

}

// 打开文件输入输出流

if(avformat_find_stream_info(pFormatCtx,NULL)<0){

cout << “ 视频文件不可读 ” <<endl;

return;

}

// 打印

av_dump_format(pFormatCtx, -1, filepath, NULL);

// 寻找视频帧的下标(视频文件存在视频、音频、字幕等轨道)

v_index = -1;

for(int i=0;i<pFormatCtx->nb_streams;i++){

if(pFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO){

v_index = i;

break;

}

}

// 判断是否存在视频帧

if(v_index < 0){

cout << “ 目标不是视频文件 ” <<endl;

return;

}

// 计算需要 delay 的毫秒数

int fps=pFormatCtx->streams[v_index]->avg_frame_rate.num/pFormatCtx->streams[v_index]->avg_frame_rate.den;// 每秒多少帧

dely_time = 1000/fps;

// 获取解码器上下文对象

pCodecCtx = avcodec_alloc_context3(NULL);

// 根据解码上下文初始化解码器上下文

if (avcodec_parameters_to_context(pCodecCtx , pFormatCtx->streams[v_index]->codecpar) < 0)

{

cout << “ 拷贝解码器数据失败 ” << endl;

return;

}

// 获取解码器对象

pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

// 打开解码器

if (avcodec_open2(pCodecCtx, pCodec, NULL) != 0) {

cout << “ 解码器打开失败 ” << endl;

return;

}

// 确认上下文和解码器都没有问题后,申请解码所需要的结构体空间

pFrame = av_frame_alloc();

pFrameYUV = av_frame_alloc();

packet = av_packet_alloc();

// 根据 YUV 数据格式,计算解码后图片的大小

v_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

// 申请缓存对象

out_buffer = (uint8_t *)av_malloc(v_size);

// 将自定义缓存空间绑定到输出的 AVFrame 中

av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize,out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

while (av_read_frame(pFormatCtx, packet) >= 0) {// 读取一个帧数据

if (packet->stream_index == v_index) {// 判断是否是视频帧

if (avcodec_send_packet(pCodecCtx, packet) != 0){// 发送数据进行解码

cout << “ 发送解码数据出错 ” << endl;

return;

}

if (avcodec_receive_frame(pCodecCtx, pFrame) != 0)

{

cout << “ 接受解码数据出错,解码时发生错误 ”;

return;

}

// 解码完成,pFrame 为解码后的数据

}

}

解码的流程,就是这样;但是里面没有说如何显示?还有一些变量没有用?

嗯,接下来,我们使用 SDL 来进行视频数据的显示;

打开 http://www.libsdl.org/,然后下载文件


配置 SDL,跟配置 FFmpge 一样;需要把把头文件、lib、dll 进行配置,参考上面的配置,不在赘述;

在 cpp 中包含 SDL 头文件

define SDL_MAIN_HANDLED

include <SDL2/SDL.h>

include <SDL2/SDL_main.h>

然后加入到 Play_H264_File 函数中使用,我贴完整代码了;

void Play_H264_File(LPVOID lpParam){

////////////////////FFmpeg/////////////////////

AVFormatContext*pFormatCtx;

AVCodecContext*pCodecCtx;

AVCodec*pCodec;

AVFrame*pFrame;

AVFrame*pFrameYUV;

AVPacket*packet;

uint8_t*out_buffer;

int v_index;

int v_size;

CMFCFFmpeg41Player1Dlg *dlg;

int dely_time;

///////////////////////////SDL////////////////////////

SDL_Window *mSDL_Window;

SDL_Renderer *mSDL_Renderer;

SDL_Texture *mSDL_Texture;

struct SwsContext *mSwsContext;

int screenW;

int screenH;

SDL_Rect mSDL_Rect;

dlg = (CMFCFFmpeg41Player1Dlg *)lpParam;

///////////////////////////SDL////////////////////////

if(SDL_Init(SDL_INIT_VIDEO)) {

cout << “SDL 初始化失败 ” <<endl;

return;

}

/////////////////////////FFmpeg///////////////////////////

char filepath[250]={0};

GetWindowTextA(dlg->mVideoEdit,(LPSTR)filepath,250);

pFormatCtx = avformat_alloc_context();

if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){

cout << “ 视频文件打开失败 ” << endl;

return;

}

if(avformat_find_stream_info(pFormatCtx,NULL)<0){

cout << “ 视频文件不可读 ” <<endl;

return;

}

av_dump_format(pFormatCtx, -1, filepath, NULL);

v_index = -1;

for(int i=0;i<pFormatCtx->nb_streams;i++){

if(pFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO){

v_index = i;

break;

}

}

if(v_index < 0){

cout << “ 目标不是视频文件 ” <<endl;

return;

}

int fps=pFormatCtx->streams[v_index]->avg_frame_rate.num/pFormatCtx->streams[v_index]->avg_frame_rate.den;// 每秒多少帧

dely_time = 1000/fps;

cout << “ 视频 FPS = ” << fps << endl;

cout << “dely_time = ” << dely_time << endl;

pCodecCtx = avcodec_alloc_context3(NULL);

//pCodecCtx = pFormatCtx->streams[videoindex]->codec;

if (avcodec_parameters_to_context(pCodecCtx , pFormatCtx->streams[v_index]->codecpar) < 0)

{

cout << “ 拷贝解码器失败 ” << endl;

return;

}

pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

if (avcodec_open2(pCodecCtx, pCodec, NULL) != 0) {

cout << “ 解码器打开失败 ” << endl;

return;

}

//sw = SDL_CreateWindow(“video”, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 680, 540, SDL_WINDOW_OPENGL);

mSDL_Rect.w = pCodecCtx->width;

mSDL_Rect.h = pCodecCtx->height;

mSDL_Window = SDL_CreateWindowFrom(dlg->GetDlgItem(IDC_VIDEO_SURFACE)->GetSafeHwnd());

mSDL_Renderer = SDL_CreateRenderer(mSDL_Window, -1, 0);

mSDL_Texture = SDL_CreateTexture(mSDL_Renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);

mSwsContext = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P,

SWS_BICUBIC, NULL, NULL, NULL);

pFrame = av_frame_alloc();

pFrameYUV = av_frame_alloc();

packet = av_packet_alloc();

v_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

out_buffer = (uint8_t *)av_malloc(v_size);

av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize,out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

while (av_read_frame(pFormatCtx, packet) >= 0) {

if (packet->stream_index == v_index) {

if (avcodec_send_packet(pCodecCtx, packet) != 0){

cout << “ 发送解码数据出错 ” << endl;

return;

}

if (avcodec_receive_frame(pCodecCtx, pFrame) != 0)

{

cout << “ 接受解码数据出错 ”;

return;

}

sws_scale(mSwsContext, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);

SDL_UpdateTexture(mSDL_Texture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);

SDL_RenderClear(mSDL_Renderer);

SDL_RenderCopy(mSDL_Renderer, mSDL_Texture, NULL, NULL);

SDL_RenderPresent(mSDL_Renderer);

Sleep(dely_time);

}

}

av_free(out_buffer);

av_frame_free(&pFrameYUV);

av_frame_free(&pFrame);

av_packet_free(&packet);

sws_freeContext(mSwsContext);

SDL_DestroyTexture(mSDL_Texture);

SDL_DestroyRenderer(mSDL_Renderer);

SDL_DestroyWindow(mSDL_Window);

SDL_Quit();

avcodec_free_context(&pCodecCtx);

avformat_close_input(&pFormatCtx);

avformat_free_context(pFormatCtx);

cout << “ 视频播放完成 ” << endl;

return;

}

代码不够完善,但是功能已经实现;大家可以拓展;暂停功能就是停止解码,加一个变量控制就可以,停止就是把解码线程停止,也是加一个变量进行控制;

原文链接:https://blog.csdn.net/dong923…

退出移动版