行业知识
【项目教程】FFmpeg+SDL2实现视频播放器
一、前言
学习ffmpeg和sdl,并编写一个视频播放器,是一个很好的音视频开发项目。
虽然关于视频播放器的原理已经有很多人在博客中进行了讲解,但是很多人不提供视频和代码,这也是我写这篇博客的主要原因。
二、在视频播放器中,主要涉及以下几个基本原理:
2.1 视频文件解封装
视频文件通常将音频和视频数据进行封装,因此在处理视频文件时,首先需要进行解封装操作,将视频流和音频流的压缩编码数据分离。常见的封装格式有MP4、MKV、FLV、AVI、RMVB、TS等。例如,解封装FLV格式的文件可能会得到H.264编码的视频流和AAC编码的音频流。
在FFMPEG中,解封装的过程如下所示:
这一步最重要的是得到解封装器的上下文结构体"AVFormatContext *m_pFormatCtx", 以及接下来我们要解码的音视频流索引。
2.2 音视频解码
原始数据通常经过压缩编码,解码过程则是将H.264、AAC等压缩后的数据解码为非压缩的音频/视频原始数据,其中视频一般为YUV或RGB数据,音频一般为PCM采样数据。
解码的步骤如下:
2.3 使用SDL2播放视频数据
我们知道视频是由连续的一帧帧图像快速播放形成的动态效果,一般设置为每秒播放25帧图像。
在播放视频时,我们使用SDL2库。每个图像在SDL2中被表示为一个纹理,而纹理与SDL2的渲染器相关联。
在视频解码后,我们可以从avcodec_receive_frame函数中获取到一个AVFrame对象,该对象包含了一帧视频数据。我们的目标是将这一帧的数据渲染到SDL的渲染器中。总体流程如下所示:
首先,我们需要使用sws_scale函数对获取到的AVFrame数据进行大小和格式的转换。接下来,我们需要更新SDL2中的纹理(Texture)和渲染器(Render)。以下是相关的关键代码示例:
AVFrame *frame = m_videoFrameQueue.front();m_videoFrameQueue.pop();AVFrame *frameYUV = av_frame_alloc();int ret = av_image_alloc(frameYUV->data, frameYUV->linesize, m_sdlRect.w, m_sdlRect.h, AV_PIX_FMT_YUV420P, 1);//Convert imageif (m_imgConvertCtx){sws_scale(m_imgConvertCtx, frame->data, frame->linesize, 0, m_videoCodecParams.height, frameYUV->data, frameYUV->linesize);SDL_UpdateYUVTexture(m_sdlTexture, NULL, frameYUV->data[0], frameYUV->linesize[0], frameYUV->data[1], frameYUV->linesize[1], frameYUV->data[2], frameYUV->linesize[2]);SDL_RenderClear(m_sdlRender);SDL_RenderCopy(m_sdlRender, m_sdlTexture, NULL, &m_sdlRect);// Present pictureSDL_RenderPresent(m_sdlRender);} 2.4 使用SDL2播放音频数据
对于音频数据,在使用avcodec_receive_frame函数接收到AVFrame后,我们得到的是音频的PCM数据。
与视频数据不同,音频数据并不是以帧为单位表示的,它可能包含多个采样数据(samples)。 为了播放音频,我们同样需要对音频数据进行格式转换,以适应音频设备的播放要求。音频格式转换主要通过swr_convert函数来完成。转换后的音频数据可以存放在一个公共缓冲区中。
使用SDL_OpenAudio函数进行音频播放,该函数需要传入一个SDL_AudioSpec结构体来设置播放参数。其中需要设置一个回调函数(callback),用于在音频设备需要获取数据时执行。因此,我们需要在此回调函数中向音频设备提供数据,实现数据的"喂养":
SDL_AudioSpec m_sdlAudioSpec;auto audioCtx = m_audioDecoder.GetCodecContext();m_sdlAudioSpec.freq = audioCtx->sample_rate; //根据你录制的PCM采样率决定m_sdlAudioSpec.format = AUDIO_S16SYS;m_sdlAudioSpec.channels = audioCtx->channels;m_sdlAudioSpec.silence = 0;m_sdlAudioSpec.samples = SDL_AUDIO_BUFFER_SIZE;m_sdlAudioSpec.callback = &SDLVideoPlayer::ReadAudioData;m_sdlAudioSpec.userdata = NULL;int re = SDL_OpenAudio(&m_sdlAudioSpec, NULL);if (re
上一篇:液冷服务器怎么操作?