FFmpeg+SDL2实现音视频播放器项目

news2024/10/24 18:27:21

一、FFmpeg视频解码器

1.视频解码知识

1).纯净的视频解码流程

压缩编码数据->像素数据。

例如解码H.264,就是“H.264码流->YUV”。

2).一般的视频解码流程

视频码流一般存储在一定的封装格式(例如MP4、AVI等)中。封装格式中通常还包含音频码流等内容。

对于封装格式中的视频,需要先从封装格式中提取中视频码流,然后再进行解码。

例如解码MKV格式的视频文件,就是“MKV->H.264码流->YUV”

2.VC下FFmpeg开发环境的搭建

新建控制台工程

打开visual studio

文件 新建 项目 win32控制台应用程序

拷贝FFmpeg开发文件

头文件(*.h)拷贝至项目文件夹的include子文件夹下

导入库文件(*.lib)拷贝至项目文件夹的lib子文件夹下

动态库文件(*.dll)拷贝至项目文件夹下

PS:如果直接使用官网上下载的FFmpeg开发文件。则可能还需要将MinGW安装目录中的inttypes.h,stdint.h,_mingw.h三个文件拷贝至项目文件夹的include子文件夹下。

配置开发文件

打开属性面板

解决方案资源管理器->右键单击项目->属性

头文件配置

配置属性->C/C++->常规->附加包含目录,输入“include”(刚才拷贝头文件的目录)

导入库配置

配置属性->链接器->常规->附加库目录,输入“lib” (刚才拷贝库文件的目录)

配置属性->链接器->输入->附加依赖项,输入“avcodec.lib; avformat.lib; avutil.lib; avdevice.lib; avfilter.lib; postproc.lib; swresample.lib; swscale.lib”(导入库的文件名)

动态库不用配置

main()中调用一个FFmpeg的接口函数

例如下面代码打印出了FFmpeg的配置信息

int main(int argc, char* argv[]){
printf("%s", avcodec_configuration());
return 0;
}

如果运行无误,则代表FFmpeg已经配置完成。

3.FFmpeg简介

FFmpeg一共包含8个库:

avcodec:编解码(最重要的库)。

avformat:封装格式处理。

avfilter:滤镜特效处理。

avdevice:各种设备的输入输出。

avutil:工具库(大部分库都需要这个库的支持)。

postproc:后加工。

swresample:音频采样数据格式转换。

swscale:视频像素数据格式转换。

1).FFmpeg解码流程图见图1:

2).FFmpeg解码函数简介

av_register_all():注册所有组件。

avformat_open_input():打开输入视频文件。

avformat_find_stream_info():获取视频文件信息。

avcodec_find_decoder():查找解码器。

avcodec_open2():打开解码器。

av_read_frame():从输入文件读取一帧压缩数据。

avcodec_decode_video2():解码一帧压缩数据。

avcodec_close():关闭解码器。

avformat_close_input():关闭输入视频文件。

3).FFmpeg相关结构体(类型)见图2:

I、FFmpeg数据结构简介

AVFormatContext

封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息。

AVInputFormat

每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体。

AVStream

视频文件中每个视频(音频)流对应一个该结构体。

AVCodecContext

编码器上下文结构体,保存了视频(音频)编解码相关信息。

AVCodec

每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。

AVPacket

存储一帧压缩编码数据。

AVFrame

存储一帧解码后像素(采样)数据。

II、FFmpeg数据结构分析

AVFormatContext

iformat:输入视频的AVInputFormat

nb_streams :输入视频的AVStream 个数

streams :输入视频的AVStream []数组

duration :输入视频的时长(以微秒为单位)

bit_rate :输入视频的码率

AVInputFormat

name:封装格式名称

long_name:封装格式的长名称

extensions:封装格式的扩展名

id:封装格式ID

一些封装格式处理的接口函数

AVStream

id:序号

codec:该流对应的AVCodecContext

time_base:该流的时基

r_frame_rate:该流的帧率

AVCodecContext

codec:编解码器的AVCodec

width, height:图像的宽高(只针对视频)

pix_fmt:像素格式(只针对视频)

sample_rate:采样率(只针对音频)

channels:声道数(只针对音频)

sample_fmt:采样格式(只针对音频)

AVCodec

name:编解码器名称

long_name:编解码器长名称

type:编解码器类型

id:编解码器ID

一些编解码的接口函数

AVPacket

pts:显示时间戳

dts :解码时间戳

data :压缩编码数据

size :压缩编码数据大小

stream_index :所属的AVStream

AVFrame

data:解码后的图像像素数据(音频采样数据)。

linesize:对视频来说是图像中一行像素的大小;对音频来说是整个音频帧的大小。

width, height:图像的宽高(只针对视频)。

key_frame:是否为关键帧(只针对视频) 。

pict_type:帧类型(只针对视频) 。例如I,P,B。

III、注意事项

裸流文件,即如h264文件,是没有时长等信息的,封装格式的才有

数据结构第一层为封装格式相关的,下面层为编解码相关的

其中

AVStream中的time_base 表示该流的时基,也就是时间的基数,简单可理解为时间的单位,比如时间数是2,时基是1s,则时间为2s

AVPacket:可以理解为h264中的数据包,编码后的包

其中,pts:显示时间戳,表示该视频帧几分几秒的时候放到屏幕上。这个值只是一个整数 没有单位,所以要得到具体的几分几秒需要和前面的time_base时基参数相乘得到。

dts:解码时间戳

stream_index:标识,表示该流属于音频流还是视频流。

data:压缩编码的数据,如h264的编码,就可以取出该数据保存起来形成的文件就是h264文件。

通过av_read_frame可从大的数据结构指针中读取到AVPacket数据

注意播放的顺序不一定按照存储的顺序来。即有一个显示的顺序和一个解码的顺序。

AVFrame

data:解码后的图像像素数据,如YUV等,可形成yuv文件(依次存储的是y、u、v分量,其中u 宽和高都只有y一半,整个数据量就y的四分之一,v的也一样)。data是一个指针数组,其中一个分量对应一个数组

数据先通过解码函数avcodec_decode_video2进行解码,然后由于解码出来的数据由于是有黑边(多出一块多余的数据),所以还需用sws_scale函数把这块多余的裁了。

4).解码后的数据要经过sws_scale()函数处理

解码后YUV格式的视频像素数据保存在AVFrame的data[0]、data[1]、data[2]中。但是这些像素值并不是连续存储的,每行有效像素之后存储了一些无效像素。

以亮度Y数据为例,data[0]中一共包含了linesize[0]*height个数据。但是出于优化等方面的考虑,linesize[0]实际上并不等于宽度width,而是一个比宽度大一些的值。

因此需要使用sws_scale()进行转换。转换后去除了无效数据,width和linesize[0] 取值相等。如下图3:


其中,sws_scale()函数需要用到的转换信息,即第一个参数,是由sws_getContext函数获得的

转换后保存在一个新的帧数据结构体,由于使用的转换函数而不是解码函数,所以这个结构体还需要填充其内部的缓冲区,用于存储像素数据,填充的方法使用avpicture_fill函数:

int avpicture_fill(AVPicture *picture, uint8_t *ptr,int pix_fmt, int width, int height);

这个函数的使用本质上是为已经分配的空间的结构体(AVPicture *)ptFrame挂上一段用于保存数据的空间,

这个结构体中有一个指针数组data[AV_NUM_DATA_POINTERS],挂在这个数组里。一般我们这么使用:

I、pFrameRGB=avcodec_alloc_frame();

II、numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,pCodecCtx->height);

buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

III、avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,pCodecCtx->width, pCodecCtx->height);

以上就是为pFrameRGB挂上buffer。这个buffer是用于存缓冲数据的。

ptFrame为什么不用fill空间。主要是下面这句:

avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,packet.data, packet.size);

很可能是ptFrame已经挂上了packet.data,所以就不用fill了。

二、SDL视频显示

SDL(Simple DirectMedia Layer)库的作用说白了就是封装了复杂的视音频底层交互工作,简化了视音频处理的难度。

主要用来做游戏,现在只用到其视频显示部分。

特点:跨平台,开源

1.库的结构图见图4:

实际上它调用了底层api完成了和硬件的交互,比如linux下,就操作了framebuffer.

2.配置vc工程和之前ffmpeg的配置几乎都是一样的:

1).新建控制台工程

打开VC++

文件->新建->项目->Win32控制台应用程序

2).拷贝SDL开发文件

头文件(*.h)拷贝至项目文件夹的include子文件夹下

导入库文件(*.lib)拷贝至项目文件夹的lib子文件夹下

动态库文件(*.dll)拷贝至项目文件夹下

3).配置开发文件

打开属性面板

解决方案资源管理器->右键单击项目->属性

4).头文件配置

配置属性->C/C++->常规->附加包含目录,输入“include”(刚才拷贝文件的目录)

5).导入库配置

配置属性->链接器->常规->附加库目录,输入“lib” (刚才拷贝文件的目录)

配置属性->链接器->输入->附加依赖项,输入“SDL2.lib; SDL2main.lib”(导入库的文件名)

6).动态库不用配置

7).是否配置成功,调用sdl的初始化函数,查看其返回值即可确定:

创建源代码文件

在工程中创建一个包含main()函数的C/C++文件(如果已经有了可以跳过这一步),后续步骤在该文件中编写源代码。

包含头文件

如果是C语言中使用SDL,则直接使用下面代码

#include "SDL2/SDL.h"

如果是C++语言中使用SDL,则使用下面代码

extern "C"
{
#include "SDL2/SDL.h"
}
main()中调用一个SDL的接口函数
例如下面代码初始化了SDL
int main(int argc, char* argv[]){
if(SDL_Init(SDL_INIT_VIDEO)) {
printf( "Could not initialize SDL - %s\n", SDL_GetError());
} else{
printf("Success init SDL");
}
return 0;
}

如果运行无误,则代表SDL已经配置完成。

3.SDL视频显示的流程图见图5:

其中特别要注意的是创建纹理数据时要传入渲染器,原因是,

纹理数据依赖于渲染器,只有通过渲染器创建才能得到该渲染器合法的纹理数据((数据格式)符合该渲染器的要求),

填充了像素数据且符合要求的纹理数据才可以拷贝给渲染器,并且渲染器才能正确显示出来。

1).SDL视频显示函数简介

SDL_Init():初始化SDL系统

SDL_CreateWindow():创建窗口SDL_Window

SDL_CreateRenderer():创建渲染器SDL_Renderer

SDL_CreateTexture():创建纹理SDL_Texture

SDL_UpdateTexture():设置纹理的数据

SDL_RenderCopy():将纹理的数据拷贝给渲染器

SDL_RenderPresent():显示

SDL_Delay():工具函数,用于延时。

SDL_Quit():退出SDL系统

其中SDL_Delay 延时函数,控制显示的速度,即控制帧率。通常每秒25帧,所以通常延时也就是40ms

2).SDL视频显示的数据结构

I、SDL视频显示的数据结构如图6所示:

II、SDL数据结构简介

SDL_Window

代表了一个“窗口”

SDL_Renderer

代表了一个“渲染器”

SDL_Texture

代表了一个“纹理”

SDL_Rect

一个简单的矩形结构

具体来说,

SDL_Window 窗口,类似弹出的窗口

SDL_Renderer 渲染器,把纹理数据画(渲染)到window上

一个window上不仅仅只有一副画面,类似多组监控画面,即一个window可对应多个yuv数据

SDL_Rect,正方形结构,存了矩形的坐标,长宽,以便确定纹理数据画在哪个位置,确定位置用,比如画在左上角就用这个来确定。被渲染器调用

SDL_Rect中的x y值是左上角为圆点开始的坐标值,调整x y值以及w h值,就可以实现在窗口的指定位置显示,没有画面的地方为黑框。

当x y等于0,w h等于窗口的宽高时即为全屏显示,此时调整宽高大小,只需调整窗口大小即可。

4.进阶-SDL中事件和多线程

1).SDL多线程

函数

SDL_CreateThread():创建一个线程

数据结构

SDL_Thread:线程的句柄

SDL事件

函数

SDL_WaitEvent()等待一个事件

SDL_PushEvent()发送一个事件

数据结构

SDL_Event:代表一个事件

SDL中事件和多线程,可用于解决上个程序播放过程中鼠标不能动的问题,

使用事件,等待事件中,就会响应鼠标键盘等事件,就不会卡在那了

将延时操作放到一个子线程中,用事件通知主线程,这样主线程就不用延时了,就可以及时响应事件了

SDL_WINDOWENVENT sdl系统自带的事件,当拉伸窗口的时候会触发

SDL_QUIT 也是SDL自带的事件,当点击窗口的×时触发

三、FFmpeg+SDL视频播放器

1.FFmpeg+SDL整合之后实现:视频文件->YUV->屏幕

2.SDL_UpdateTexture函数最后一个参数表示的是,一行像素数据的数据量

窗口的宽和高无所谓,但是纹理数据的宽和高要和视频数据的宽和高一致。

3.对于大数据的调试,如果直接打印出来,数据太多不好分析,一般是写入到文件,然后用工具打开文件去分析。

比如视频像素数据,可以写入到yuv文件,再用yuv分析工具来分析。

4.脱离开发环境的独立播放器

在解决方案中的debug目录下的*.exe文件即为编译好的可执行文件。

执行这个文件就可以脱离开发环境运行。

5.调试时,在属性->调试->命令参数 中填的东西,就是程序的输入参数,也就是argv中的内容

平时独立的exe程序,只需在执行的时候后面加上参数就是输入参数了,如ffplay.exe那样。

四、FFmpeg+SDL视频播放器代码实现

主要是FFmpegAndSDL.cpp文件,代码如下(基本上每一行都有注释):

1 /*****************************************************************************
  2 * Copyright (C) 2017-2020 Hanson Yu  All rights reserved.
  3 ------------------------------------------------------------------------------
  4 * File Module       :     FFmpegAndSDL.cpp
  5 * Description       :     FFmpegAndSDL Demo
  6 
  7 
  8 * Created           :     2017.09.21.
  9 * Author            :     Yu Weifeng
 10 * Function List     :     
 11 * Last Modified     :     
 12 * History           :     
 13 * Modify Date      Version         Author           Modification
 14 * -----------------------------------------------
 15 * 2017/09/21      V1.0.0         Yu Weifeng       Created
 16 ******************************************************************************/
 17 
 18 #include "stdafx.h"
 19 #include <stdio.h>
 20 
 21 
 22 /*解决错误:
 23 LNK2019    无法解析的外部符号 __imp__fprintf,该符号在函数 _ShowError 中被引用
 24 
 25 原因:
 26 ……这是链接库问题
 27 就是工程里面没有添加那两个函数需要的库,#progma这个是代码链接库
 28 第二句是vs2015兼容的问题。
 29 lib库的vs编译版本 和 工程的vs开发版本 不一致。
 30 导出函数定义变了。所以要人为加一个函数导出。
 31 */
 32 #pragma comment(lib, "legacy_stdio_definitions.lib")
 33 extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; }
 34 
 35 /*
 36 __STDC_LIMIT_MACROS and __STDC_CONSTANT_MACROS are a workaround to allow C++ programs to use stdint.h
 37 macros specified in the C99 standard that aren't in the C++ standard. The macros, such as UINT8_MAX, INT64_MIN,
 38 and INT32_C() may be defined already in C++ applications in other ways. To allow the user to decide
 39 if they want the macros defined as C99 does, many implementations require that __STDC_LIMIT_MACROS
 40 and __STDC_CONSTANT_MACROS be defined before stdint.h is included.
 41 
 42 This isn't part of the C++ standard, but it has been adopted by more than one implementation.
 43 */
 44 #define __STDC_CONSTANT_MACROS
 45 
 46 extern "C"
 47 {
 48 #include "libavcodec/avcodec.h"
 49 #include "libavformat/avformat.h"
 50 #include "libswscale/swscale.h"
 51 #include "SDL2/SDL.h"
 52 };
 53 
 54 
 55 //Refresh Event 自定义事件
 56 #define PLAY_REFRESH_EVENT       (SDL_USEREVENT + 1)//自定义刷新图像(播放)事件
 57 #define PLAY_BREAK_EVENT         (SDL_USEREVENT + 2) //自定义退出播放事件
 58 
 59 
 60 static int g_iThreadExitFlag = 0;
 61 /*****************************************************************************
 62 -Fuction        : RefreshPlayThread
 63 -Description    : RefreshPlayThread
 64 -Input          : 
 65 -Output         : 
 66 -Return         : 
 67 * Modify Date      Version         Author           Modification
 68 * -----------------------------------------------
 69 * 2017/09/21      V1.0.0         Yu Weifeng       Created
 70 ******************************************************************************/
 71 int RefreshPlayThread(void *opaque) 
 72 {
 73     g_iThreadExitFlag = 0;
 74     SDL_Event tEvent={0};
 75     
 76     while (!g_iThreadExitFlag) 
 77     {
 78         tEvent.type = PLAY_REFRESH_EVENT;
 79         SDL_PushEvent(&tEvent);//发送事件给其他线程
 80         SDL_Delay(20);//延时函数 填40的时候,视频会有种卡的感觉
 81     }
 82     //Break
 83     g_iThreadExitFlag = 0;
 84     tEvent.type = PLAY_BREAK_EVENT;
 85     SDL_PushEvent(&tEvent);//发送事件给其他线程 发送一个事件
 86 
 87     return 0;
 88 }
 89 
 90 /*****************************************************************************
 91 -Fuction        : main
 92 -Description    : main
 93 -Input          : 
 94 -Output         : 
 95 -Return         : 
 96 * Modify Date      Version         Author           Modification
 97 * -----------------------------------------------
 98 * 2017/09/21      V1.0.0         Yu Weifeng       Created
 99 ******************************************************************************/
100 int main(int argc, char* argv[])
101 {
102     /*------------FFmpeg----------------*/
103     const char *strFilePath = "屌丝男士.mov";
104     AVFormatContext    *ptFormatContext = NULL;//封装格式上下文,内部包含所有的视频信息
105     int                i = 0; 
106     int             iVideoindex=0;//纯视频信息在音视频流中的位置,也就是指向音视频流数组中的视频元素
107     AVCodecContext    *ptCodecContext;//编码器相关信息上下文,内部包含编码器相关的信息,指向AVFormatContext中的streams成员中的codec成员
108     AVCodec            *ptCodec;//编码器,使用函数avcodec_find_decoder或者,该函数需要的id参数,来自于ptCodecContext中的codec_id成员
109     AVFrame            *ptFrame=NULL;//存储一帧解码后像素(采样)数据
110     AVFrame            *ptFrameAfterScale=NULL;//存储(解码数据)转换后的像素(采样)数据
111     unsigned char   *pucFrameAfterScaleBuf=NULL;//用于存储ptFrameAfterScale中的像素(采样)缓冲数据
112     AVPacket        *ptPacket=NULL;//存储一帧压缩编码数据
113     int             iRet =0;
114     int             iGotPicture=0;//解码函数的返回参数,got_picture_ptr Zero if no frame could be decompressed, otherwise, it is nonzero
115 
116     /*------------SDL----------------*/
117     int iScreenWidth=0, iScreenHeight=0;//视频的宽和高,指向ptCodecContext中的宽和高
118     SDL_Window *ptSdlWindow=NULL;//用于sdl显示视频的窗口(用于显示的屏幕)
119     SDL_Renderer* ptSdlRenderer=NULL;//sdl渲染器,把纹理数据画(渲染)到window上
120     SDL_Texture* ptSdlTexture=NULL;//sdl纹理数据,用于存放像素(采样)数据,然后给渲染器
121     SDL_Rect tSdlRect ={0};//正方形矩形结构,存了矩形的坐标,长宽,以便确定纹理数据画在哪个位置,确定位置用,比如画在左上角就用这个来确定。被渲染器调用
122     SDL_Thread *ptVideoControlTID=NULL;//sdl线程id,线程的句柄
123     SDL_Event tSdlEvent = {0};//sdl事件,代表一个事件
124 
125     /*------------像素数据处理----------------*/
126     struct SwsContext *ptImgConvertInfo;//图像转换(上下文)信息,图像转换函数sws_scale需要的参数,由sws_getContext函数赋值
127 
128 
129 
130     /*------------FFmpeg----------------*/
131     av_register_all();//注册FFmpeg所有组件
132     avformat_network_init();//初始化网络组件
133     
134     ptFormatContext = avformat_alloc_context();//分配空间给ptFormatContext
135     if (avformat_open_input(&ptFormatContext, strFilePath, NULL, NULL) != 0) 
136     {//打开输入视频文件
137         printf("Couldn't open input stream.\n");
138         return -1;
139     }
140     if (avformat_find_stream_info(ptFormatContext, NULL)<0) 
141     {//获取视频文件信息
142         printf("Couldn't find stream information.\n");
143         return -1;
144     }
145     //获取编码器相关信息上下文,并赋值给ptCodecContext
146     iVideoindex = -1;
147     for (i = 0; i<ptFormatContext->nb_streams; i++)
148     {
149         if (ptFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) 
150         {
151             iVideoindex = i;
152             break;
153         }
154     }
155     if (iVideoindex == -1) 
156     {
157         printf("Didn't find a video stream.\n");
158         return -1;
159     }
160     ptCodecContext = ptFormatContext->streams[iVideoindex]->codec;
161     
162     ptCodec = avcodec_find_decoder(ptCodecContext->codec_id);//查找解码器
163     if (ptCodec == NULL) 
164     {
165         printf("Codec not found.\n");
166         return -1;
167     }
168     if (avcodec_open2(ptCodecContext, ptCodec, NULL)<0) 
169     {//打开解码器
170         printf("Could not open codec.\n");
171         return -1;
172     }
173     
174     ptPacket = (AVPacket *)av_malloc(sizeof(AVPacket));//分配保存解码前数据的空间
175     ptFrame = av_frame_alloc();//分配结构体空间,结构体内部的指针指向的数据暂未分配,用于保存图像转换前的像素数据
176     
177     /*------------像素数据处理----------------*/
178     ptFrameAfterScale = av_frame_alloc();//分配结构体空间,结构体内部的指针指向的数据暂未分配,用于保存图像转换后的像素数据
179     pucFrameAfterScaleBuf = (uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, ptCodecContext->width, ptCodecContext->height));//分配保存数据的空间
180      /*int avpicture_fill(AVPicture *picture, uint8_t *ptr,int pix_fmt, int width, int height);
181     这个函数的使用本质上是为已经分配的空间的结构体(AVPicture *)ptFrame挂上一段用于保存数据的空间,
182     这个结构体中有一个指针数组data[AV_NUM_DATA_POINTERS],挂在这个数组里。一般我们这么使用:
183     1) pFrameRGB=avcodec_alloc_frame();
184     2) numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,pCodecCtx->height);
185         buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
186     3) avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,pCodecCtx->width, pCodecCtx->height);
187     以上就是为pFrameRGB挂上buffer。这个buffer是用于存缓冲数据的。
188     ptFrame为什么不用fill空间。主要是下面这句:
189     avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,packet.data, packet.size);
190     很可能是ptFrame已经挂上了packet.data,所以就不用fill了。*/
191     avpicture_fill((AVPicture *)ptFrameAfterScale, pucFrameAfterScaleBuf, PIX_FMT_YUV420P, ptCodecContext->width, ptCodecContext->height);    
192     //sws开头的函数用于处理像素(采样)数据
193     ptImgConvertInfo = sws_getContext(ptCodecContext->width, ptCodecContext->height, ptCodecContext->pix_fmt,
194         ptCodecContext->width, ptCodecContext->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);//获取图像转换(上下文)信息
195 
196     /*------------SDL----------------*/
197     if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER)) 
198     {//初始化SDL系统
199         printf("Could not initialize SDL - %s\n", SDL_GetError());
200         return -1;
201     }
202     //SDL 2.0 Support for multiple windows
203     iScreenWidth = ptCodecContext->width;
204     iScreenHeight = ptCodecContext->height;
205     ptSdlWindow = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
206         iScreenWidth, iScreenHeight, SDL_WINDOW_OPENGL);//创建窗口SDL_Window
207 
208     if (!ptSdlWindow) 
209     {
210         printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
211         return -1;
212     }
213     ptSdlRenderer = SDL_CreateRenderer(ptSdlWindow, -1, 0);//创建渲染器SDL_Renderer
214     //IYUV: Y + U + V  (3 planes)
215     //YV12: Y + V + U  (3 planes)
216     //创建纹理SDL_Texture
217     ptSdlTexture = SDL_CreateTexture(ptSdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, ptCodecContext->width, ptCodecContext->height);
218 
219     tSdlRect.x = 0;//x y值是左上角为圆点开始的坐标值,调整x y值以及w h值,就可以实现在窗口的指定位置显示,没有画面的地方为黑框
220     tSdlRect.y = 0;//当x y等于0,w h等于窗口的宽高时即为全屏显示,此时调整宽高大小,只需调整窗口大小即可
221     tSdlRect.w = iScreenWidth;
222     tSdlRect.h = iScreenHeight;
223 
224     ptVideoControlTID = SDL_CreateThread(RefreshPlayThread, NULL, NULL);//创建一个线程
225     
226     while (1) 
227     {//Event Loop        
228         SDL_WaitEvent(&tSdlEvent);//Wait,等待其他线程过来的事件
229         if (tSdlEvent.type == PLAY_REFRESH_EVENT) //自定义刷新图像(播放)事件
230         {
231             /*------------FFmpeg----------------*/
232             if (av_read_frame(ptFormatContext, ptPacket) >= 0) //从输入文件读取一帧压缩数据
233             {
234                 if (ptPacket->stream_index == iVideoindex) 
235                 {
236                     iRet = avcodec_decode_video2(ptCodecContext, ptFrame, &iGotPicture, ptPacket);//解码一帧压缩数据
237                     if (iRet < 0) 
238                     {
239                         printf("Decode Error.\n");
240                         return -1;
241                     }
242                     if (iGotPicture) 
243                     {
244                         //图像转换,sws_scale()函数需要用到的转换信息,即第一个参数,是由sws_getContext函数获得的
245                         sws_scale(ptImgConvertInfo, (const uint8_t* const*)ptFrame->data, ptFrame->linesize, 0, ptCodecContext->height, ptFrameAfterScale->data, ptFrameAfterScale->linesize);
246 
247                         /*------------SDL----------------*/
248                         SDL_UpdateTexture(ptSdlTexture, NULL, ptFrameAfterScale->data[0], ptFrameAfterScale->linesize[0]);//设置(更新)纹理的数据
249                         SDL_RenderClear(ptSdlRenderer);//先清除渲染器里的数据
250                         //SDL_RenderCopy( ptSdlRenderer, ptSdlTexture, &tSdlRect, &tSdlRect );  //将纹理的数据拷贝给渲染器
251                         SDL_RenderCopy(ptSdlRenderer, ptSdlTexture, NULL, NULL);//将纹理的数据拷贝给渲染器
252                         SDL_RenderPresent(ptSdlRenderer);//显示
253                     }
254                 }
255                 av_free_packet(ptPacket);//释放空间
256             }
257             else 
258             {                
259                 g_iThreadExitFlag = 1;//Exit Thread
260             }
261         }
262         else if (tSdlEvent.type == SDL_QUIT) //也是SDL自带的事件,当点击窗口的×时触发//SDL_WINDOWENVENT sdl系统自带的事件,当拉伸窗口的时候会触发
263         {
264             g_iThreadExitFlag = 1;
265         }
266         else if (tSdlEvent.type == PLAY_BREAK_EVENT) //自定义退出播放事件
267         {
268             break;
269         }
270 
271     }
272     
273     /*------------像素数据处理----------------*/
274     sws_freeContext(ptImgConvertInfo);//释放空间
275     
276     /*------------SDL----------------*/
277     SDL_Quit();//退出SDL系统
278 
279     /*------------FFmpeg----------------*/
280     av_frame_free(&ptFrameAfterScale);//释放空间
281     av_frame_free(&ptFrame);//释放空间
282     avcodec_close(ptCodecContext);//关闭解码器
283     avformat_close_input(&ptFormatContext);//关闭输入视频文件
284 
285     return 0;
286 }

FFmpegAndSDL.cpp

粉丝福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1842781.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SAP赋能食品行业,确保安全与品质的双重飞跃

品安全与品质是消费者最关心的问题&#xff0c;也是食品企业的生命线。随着科技的发展和消费者需求的日益多样化&#xff0c;食品行业正面临着前所未有的挑战和机遇。SAP作为全球领先的企业资源规划&#xff08;ERP&#xff09;系统&#xff0c;为食品行业提供了全面的解决方案…

银河麒麟4.0.2安装带有opengl的Qt5.12.9

银河麒麟4.0.2下载地址&#xff1a;银河麒麟-银河麒麟(云桌面系统)-银河麒麟最新版下载v4.0.2-92下载站 VirtualBox:https://www.virtualbox.org/wiki/Downloads qt下载&#xff1a;Index of /archive/qt/5.12/5.12.9 1安装VirtualBox:网上教材比较多 1&#xff09;安装完后安…

Android14 开发之Broadcast延迟及Service常驻等新特性说明

Android14 开发之Broadcast延迟及Service常驻等新特性说明 Broadcast延迟问题 FLAG_RECEIVER_FOREGROUND 是 Android 中的一种标志&#xff0c;它用于将广播接收器&#xff08;BroadcastReceiver&#xff09;标记为前台广播。前台广播具有较高的优先级&#xff0c;系统会尽快…

T200HSA单路SDI/HDMI+1路3.5音频高清万能采集卡

产品简介&#xff1a; 同三维T200HSA单路高清万能采集卡&#xff0c;可以采集1路SDI/HDMI高清信号1路3.5音频信号&#xff0c;卡上有1个是HDMI接口1个是SDI接口1个3.5音频口&#xff0c;配件有&#xff1a; 1个小档板&#xff0c;PCI-E2.0 X1&#xff0c;分辨率最高可以达到10…

Sklearn之朴素贝叶斯应用

目录 sklearn中的贝叶斯分类器 前言 1 分类器介绍 2 高斯朴素贝叶斯GaussianNB 2.1 认识高斯朴素贝叶斯 2.2 高斯朴素贝叶斯建模案例 2.3 高斯朴素贝叶斯擅长的数据集 2.3.1 三种数据集介绍 2.3.2 构建三种数据 2.3.3 数据标准化 2.3.4 朴素贝叶斯处理数据 2.4 高斯…

Linux下手动修改服务器时间(没网环境下)

在客户服务器上更新程序时&#xff0c;发现服务器时间不对&#xff0c;现在应该是下午13:44:00&#xff0c;但服务器却显示为&#xff1a;21:40:53&#xff0c;所有是不对的。 date解决办法&#xff1a; 1、由于服务器是没有网的&#xff0c;只能手动设置时间&#xff0c;输入…

如何使用k8s安装nexus3呢

百度云盘地址 链接&#xff1a;https://pan.baidu.com/s/1YN1qc2RvzTU3Ba6L_zCTdg?pwd5z1i 提取码&#xff1a;5z1i 下载后上传到本地服务器 docker load -i nexus3 创建 nexus-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata:name: nexus3-deployment spec…

2024年华为最新笔试预约流程,超详细!内附操作流程!

未注册过华为账号步骤如下 约考试或者查询考点网址&#xff1a;https://www.pearsonvue.com.cn/Clients/huawei.aspx 1-1进入网站后先点【登录】 1-2点击【立即注册】 1-3(手机注册方式) 填写手机号、接收验证码、设置登录密码后勾选同意两个声明 (邮箱注册方式) 填写…

易保全远程视频公证:打破传统,创新公证服务新模式

在数字化浪潮的冲击下&#xff0c;公证行业也在经历着深刻的变革。传统公证方式在满足人们日益增长的公证需求时&#xff0c;逐渐显露出一些问题与困扰。与此同时&#xff0c;远程视频公证作为一种创新模式&#xff0c;正展现出强大的发展趋势和显著的优势。 近年来&#xff0…

建筑主体沉降观测规范详解

随着城市化进程的加速&#xff0c;高层建筑和大型建筑项目日益增多&#xff0c;建筑主体的沉降观测工作显得尤为重要。沉降观测是确保建筑安全稳定的关键环节&#xff0c;对于预防建筑安全事故、保障人民生命财产安全具有重要意义。本文将详细解析建筑主体沉降观测的规范和要求…

SAP之我做SAP顾问那些年的辛酸苦辣终成过往

大家好&#xff1a;CSDN也更了有一段时间&#xff0c;大家可以看到&#xff0c;最近一直没时间更新&#xff0c;这里给大家道歉&#xff0c;对不起&#xff01; 首先做为一个不算是SAP圈混的菜鸟&#xff0c;今天给大家分享一下我一路走来的薪路历程&#xff0c;比不上大厂&…

windows服务器下jenkins c语言打包的一些经验share

前言 因为一些原因&#xff0c;需要从linux环境下的jenkins 打包c语言转移到使用windows环境下的jenkins打包c语言&#xff0c;从转移的过程中&#xff0c;发现了一些问题和解决方案&#xff0c;故在此和各位运维工程师分享一下。 一、windows 下的c语言编译环境配置 这边就…

Hadoop3:MapReduce中实现自定义排序

一、场景描述 以统计号码的流量案例为基础&#xff0c;进行开发。 流量统计结果 我们现在要对这个数据的总流量进行自定义排序。 二、代码实现 我们要对总流量进行排序&#xff0c;就是对FlowBean中的sumFlow字段进行排序。 所以&#xff0c;我们需要让FlowBean实现Writab…

AI时代下的自动化代码审计工具

代码审计工具分享 吉祥学安全知识星球&#x1f517;除了包含技术干货&#xff1a;Java代码审计、web安全、应急响应等&#xff0c;还包含了安全中常见的售前护网案例、售前方案、ppt等&#xff0c;同时也有面向学生的网络安全面试、护网面试等。 这两年一直都在提“安全左移”&…

Java程序员Python一小时速成

背景 由于最近要开发一些AI LLM&#xff08;Large Language Model 大语言模型&#xff09;应用程序&#xff0c;然后又想使用LangChain&#xff08;LangChain 是一个用于构建和操作大语言模型&#xff08;LLMs&#xff09;的框架&#xff0c;旨在帮助开发者更方便地集成和使用…

JavaEE多线程(2)

文章目录 1..多线程的安全1.1出现多线程不安全的原因1.2解决多线程不安全的⽅法1.3三种典型死锁场景1.4如何避免死锁问题2.线程等待通知机制2.1等待通知的作用2.2等待通知的方法——wait2.3唤醒wait的方法——notify 1…多线程的安全 1.1出现多线程不安全的原因 线程在系统中…

前端入门篇(五十二)练习6:transition过渡小动画

所以应该先找到第n个li&#xff0c;找到li再找img&#xff0c;li没有找错&#xff0c;底下又各自只有一个img&#xff0c;解决 ul li:nth-child(1) img { } 描述文字从下往上&#xff1a; 一开始描述也在框框下面&#xff0c;当hover时&#xff0c;translateY(0)&#xff0…

redis高可用-哨兵机制

一&#xff1a;背景 上一节我们已经实现了redis的主从同步&#xff0c;从而实现服务的流量分摊和数据高可用&#xff0c;但是出现故障以后&#xff0c;需要人工手动接入&#xff0c;手动切换主从&#xff0c;来实现故障转移。这是比较麻烦的&#xff0c;毕竟人不能实时盯着服务…

如何用Vue3打造一个令人惊叹的极坐标图

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 使用 Vue3-ApexCharts 绘制极地区域图 应用场景 极地区域图常用于展示具有周期性或分类性数据的分布情况&#xff0c;例如不同月份的销售额、不同年龄段的人口分布等。 基本功能 此代码使用 Vue3-ApexChart…

打字速度对编程的影响大吗?

知道打字速度对编程的影响大吗&#xff1f;实际上&#xff0c;在编程的世界里&#xff0c;关键在于思考&#xff0c;而非打字速度。要明白&#xff0c;编程与日常聊天中的打字有着本质的区别&#xff0c;如果编程仅仅取决于打字速度&#xff0c;那它岂不就等同于打字员的工作了…