这篇文章记录一个简单视频播放器的开发过程,代码极其为简洁,基于ffmpeg最新版本4.1实现的。视频渲染用的SDL2.0,SDL视频渲染部分代码直接copy的雷神的最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)但是他这篇的代码对于高版本的ffmpeg已经不适配了。
测试视频是在Best Place on the Web to Download HD Trailers下载的电影宣传片,1080p的,下面是播放的画面。
结构体:
AVFormatContext:封装格式上下文,媒体文件的处理句柄。
AVCodeContext:解码器上下文,用于存储解码器相关信息。
AVCodec:解码器结构体
AVCodecParameters:描述媒体流的详细信息,这个结构体在早期版本是没有的,加进来应该是为了解码和解封装直接的解耦合。
AVPacket: av_read_frame()的返回结果,是从AVFormatContext中读取到的信息,就叫pkt吧,一个pkt中可能只能包含一种媒体流,音频、视频、字幕等,如果是视频流可能包含一帧视频,如果是音频流可能包含多个音频帧。
AVFrame:这个结构体就是FFmpeg解码出来的实际数据了,经由av_send_packet(),和av_receive_frame()后得到的数据AVFrame,根据软硬解码的选择不同可能有不同的数据格式,常见的有yuv420p(多见于软解码),nv12,nv21等(常见于硬解码),一般的渲染播放需要rgb数据,这部分数据可以用FFmpeg的swscale来实现,但是效率较低。
- avformat_open_input() //打开播放文件,可以是网络流或者本地文件
- avforamt_find_stream_info() //Read packets of a media file to get stream information
- av_find_best_stream() //找到媒体流对应的index
- avcodec_parameters_alloc //为AVCodecParameters分配空间
- avcodec_find_decoder //找到解码器
- avcodec_alloc_context3 //为avcodec_alloc_context3 分配空间
- avcodec_parameters_to_context() //Fill the codec context based on the values from the supplied codec
- avcodec_open2() .//打开解码器
- av_packet_alloc(); 为AVPacket分配空间
- av_frame_alloc()为AVFrame分配空间
11 .av_read_frame()从AVFormatContext中读取AVPacket。- avcodec_send_packet() //将读取的pkt发送到解码线程
- avcodec_receive_frame()从解码线程中取出解码后的数据AVFrame.
av_packet_unref(pkt);
av_frame_unref(frame);
这是第一版源码,以后有时间加入音频解码和音视频同步,再对解码解封装封装成生产者消费者模式,更符合实际生产需求。
/*
* @author wangyu
* @date 2020-08-08
* @[email protected]
*/
#include
#include
extern "C"
{
#include
#include
#include
#include
#include
#include
}
int main(int argc,char*argv[])
{
/*
* SDL变量
*/
//SDL---------------------------
int screen_w = 0, screen_h = 0;
SDL_Window* screen;
SDL_Renderer* sdlRenderer;
SDL_Texture* sdlTexture;
SDL_Rect sdlRect;
char url[] = "test.mov";
AVFormatContext* ifmt=NULL;
//打开文件
int ret=avformat_open_input(&ifmt, url, NULL, NULL);
if (ret < 0)
{
printf("can not open the input file %s\n", url);
return -1;
}
printf("open the file successed!\n");
ret =avformat_find_stream_info(ifmt, NULL);
if (ret < 0)
{
printf("avformat_find_stream_info failed\n");
return -1;
}
printf("avformat_find_stream_info successed!\n");
int video_index = -1;
int audio_index = -1;
video_index=av_find_best_stream(ifmt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
audio_index = av_find_best_stream(ifmt, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (video_index < 0)
{
printf("can not find the video stream!\n");
return - 1;
}
printf("the videostream index is %d\n", video_index);
if (audio_index < 0)
{
printf("can not find the audio stream!\n");
}
printf("the audio stream index is %d\n", audio_index);
av_dump_format(ifmt, 1, url,0);
//解码环节:
/*
* 找到解码器
* 分配解码器上下文空间
* 解码器参数复制
*/
AVCodecParameters* para = avcodec_parameters_alloc();
AVCodec* vcodec = avcodec_find_decoder(ifmt->streams[video_index]->codecpar->codec_id);
if (!vcodec)
{
printf(" can not find the vidoe decoder!\n");
return -1;
}
AVCodecContext* vctx = avcodec_alloc_context3(vcodec);
avcodec_parameters_to_context(vctx, ifmt->streams[video_index]->codecpar);
ret =avcodec_open2(vctx, vcodec, NULL);
if (ret < 0)
{
printf("avcodec_open2 failed!\n");
return -1;
}
printf("avcodec_open2() successed!\n");
/*
* SDL代码
*/
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
printf("Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}
screen_w = vctx->width;
screen_h = vctx->height;
//SDL 2.0 Support for multiple windows
screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
screen_w, screen_h,
SDL_WINDOW_OPENGL);
if (!screen) {
printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
return -1;
}
sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
//IYUV: Y + U + V (3 planes)
//YV12: Y + V + U (3 planes)
sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, vctx->width, vctx->height);
sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = screen_w;
sdlRect.h = screen_h;
/*
* SDL End
*/
AVPacket* pkt=NULL;
//av_packet_alloc中包含了av_pack_init()
pkt = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
while (1)
{
ret=av_read_frame(ifmt, pkt);
if (ret < 0)
{
printf("read failed or the file is ended!\n");
av_packet_unref(pkt);
return -1;
}
if (pkt->stream_index != video_index)
{
av_packet_unref(pkt);
continue;
}
printf("pkt's size=%d, pkt's pts =%ld\n", pkt->size, pkt->pts);
//开始解码
ret=avcodec_send_packet(vctx, pkt);
if (ret < 0)
{
printf("avcodec_send_packet failed!\n");
av_packet_unref(pkt);
continue;
}
printf("avcodec_send_packet successed!\n");
//用完packet第一时间释放内存
av_packet_unref(pkt);
ret = avcodec_receive_frame(vctx, frame);
if (ret < 0)
{
printf("avcodec_receive_frame failed!\n");
av_packet_unref(pkt);
av_frame_unref(frame);
continue;
}
printf("avcodec_receive_frame successed!\n");
printf("frame.width= %d,frame.height= %d\n",frame->width, frame->height);
/*
* SDL
*/
SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
SDL_RenderClear(sdlRenderer);
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
SDL_RenderPresent(sdlRenderer);
SDL_Delay(40);
//释放内存
av_frame_unref(frame);
/*
* SDL End
*/
//Sleep(500);//40ms
}
av_packet_free(&pkt);
av_frame_free(&frame);
//释放内存
avformat_free_context(ifmt);
avcodec_parameters_free(¶);
avcodec_free_context(&vctx);
SDL_Quit();
return 0;
}
==============================================================================
/*
* @author wangyu
* @date 2020-08-08
* @[email protected]
*/
#include
#include
extern "C"
{
#include
#include
#include
#include
#include
#include
}
#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio
//48000 * (32/8)
unsigned int audioLen = 0;
unsigned char* audioChunk = NULL;
unsigned char* audioPos = NULL;
void fill_audio(void* udata, Uint8* stream, int len)
{
SDL_memset(stream, 0, len);
if (audioLen == 0)
return;
len = (len > audioLen ? audioLen : len);
SDL_MixAudio(stream, audioPos, len, SDL_MIX_MAXVOLUME);
audioPos += len;
audioLen -= len;
}
int main(int argc, char* argv[])
{
/*
* SDL变量
*/
//SDL---------------------------
uint64_t out_chn_layout = AV_CH_LAYOUT_STEREO; //通道布局 输出双声道
enum AVSampleFormat out_sample_fmt = (AVSampleFormat)AV_SAMPLE_FMT_S16; //声音格式
int out_sample_rate = 44100; //采样率
int out_nb_samples = -1;
int out_channels = -1; //通道数
int out_buffer_size = -1; //输出buff
unsigned char* outBuff = NULL;
uint64_t in_chn_layout = -1; //通道布局
SDL_AudioSpec wantSpec;
int screen_w = 0, screen_h = 0;
SDL_Window* screen;
SDL_Renderer* sdlRenderer;
SDL_Texture* sdlTexture;
SDL_Rect sdlRect;
char url[] = "test.mov";
AVFormatContext* ifmt = NULL;
//打开文件
int ret = avformat_open_input(&ifmt, url, NULL, NULL);
if (ret < 0)
{
printf("can not open the input file %s\n", url);
return -1;
}
printf("open the file successed!\n");
ret = avformat_find_stream_info(ifmt, NULL);
if (ret < 0)
{
printf("avformat_find_stream_info failed\n");
return -1;
}
printf("avformat_find_stream_info successed!\n");
int video_index = -1;
int audio_index = -1;
video_index = av_find_best_stream(ifmt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
audio_index = av_find_best_stream(ifmt, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (video_index < 0)
{
printf("can not find the video stream!\n");
return -1;
}
printf("the videostream index is %d\n", video_index);
if (audio_index < 0)
{
printf("can not find the audio stream!\n");
}
printf("the audio stream index is %d\n", audio_index);
av_dump_format(ifmt, 1, url, 0);
//解码环节:
/*
* 视频解码器初始化工作
* 找到解码器
* 分配解码器上下文空间
* 解码器参数复制
*/
AVCodecParameters* vpara = avcodec_parameters_alloc();
vpara = ifmt->streams[video_index]->codecpar;
AVCodec* vcodec = avcodec_find_decoder(vpara->codec_id);
//AVCodec* vcodec = avcodec_find_decoder_by_name("h264_mediacodec ");
if (!vcodec)
{
printf(" can not find the video decoder!\n");
return -1;
}
AVCodecContext* vctx = avcodec_alloc_context3(vcodec);
avcodec_parameters_to_context(vctx, vpara);
ret = avcodec_open2(vctx, vcodec, NULL);
if (ret < 0)
{
printf("avcodec_open2 failed!\n");
return -1;
}
printf("avcodec_open2() successed!\n");
/*
*音频解码器初始化
*/
AVCodecParameters* apara = avcodec_parameters_alloc();
apara = ifmt->streams[audio_index]->codecpar;
AVCodec* acodec = avcodec_find_decoder(apara->codec_id);
if (!acodec)
{
//这里在完善音视频同步播放后考虑只有视频的情况,即音频没有也可以正常播放
printf("av_find_decoder failed!\n");
return -1;
}
AVCodecContext* actx = avcodec_alloc_context3(acodec);
avcodec_parameters_to_context(actx, apara);
ret=avcodec_open2(actx, acodec, NULL);
if (ret < 0)
{
printf("audio avcodec_open2() failed!\n");
return -1;
}
printf("audio avcdecp_open2() successed!\n");
/* audio codec init finished !*/
/*
* SDL代码
*/
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
printf("Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}
screen_w = vctx->width;
screen_h = vctx->height;
//SDL 2.0 Support for multiple windows
screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
screen_w, screen_h,
SDL_WINDOW_OPENGL);
if (!screen) {
printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
return -1;
}
sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
//IYUV: Y + U + V (3 planes)
//YV12: Y + V + U (3 planes)
sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, vctx->width, vctx->height);
sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = screen_w;
sdlRect.h = screen_h;
/*
* SDL Video end
* SDL Audio start
*/
//out parameter
out_nb_samples = actx->frame_size;
out_channels = av_get_channel_layout_nb_channels(out_chn_layout);
out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_fmt, 1);
outBuff = (unsigned char*)av_malloc(MAX_AUDIO_FRAME_SIZE * 2); //双声道
printf("-------->out_buffer_size is %d\n", out_buffer_size);
in_chn_layout = av_get_default_channel_layout(actx->channels);
wantSpec.freq = out_sample_rate;
wantSpec.format = AUDIO_S16SYS;
wantSpec.channels = out_channels;
wantSpec.silence = 0;
wantSpec.samples = out_nb_samples;
wantSpec.callback = fill_audio;
wantSpec.userdata = actx;
if (SDL_OpenAudio(&wantSpec, NULL) < 0)
{
printf("can not open SDL!\n");
ret = -1;
return -1;
}
//Swr
struct SwrContext* au_convert_ctx = swr_alloc();
au_convert_ctx = swr_alloc_set_opts(au_convert_ctx,
out_chn_layout, /*out*/
out_sample_fmt, /*out*/
out_sample_rate, /*out*/
in_chn_layout, /*in*/
actx->sample_fmt, /*in*/
actx->sample_rate, /*in*/
0,
NULL);
swr_init(au_convert_ctx);
SDL_PauseAudio(0);
/*
SDL Audio end
*/
int pts, Synpts;
AVPacket* pkt = NULL;
//av_packet_alloc中包含了av_pack_init()
pkt = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
bool isAudio = false;//判断去读的pkt是音频还是视频。
while (1)
{
ret = av_read_frame(ifmt, pkt);
if (ret < 0)
{
printf("read failed or the file is ended!\n");
av_packet_unref(pkt);
return -1;
}
if (pkt->stream_index == audio_index)
{
isAudio = true;
//开始解码
ret = avcodec_send_packet(actx, pkt);
if (ret < 0)
{
printf("audio avcodec_send_packet failed!\n");
av_packet_unref(pkt);
continue;
}
}
else if(pkt->stream_index == video_index)
{
isAudio = false;
ret = avcodec_send_packet(vctx, pkt);
if (ret < 0)
{
printf("video avcodec_send_packet failed!\n");
av_packet_unref(pkt);
continue;
}
}
else
{
//既不是音频流也不是视频流选择丢弃,并且释放pkt
continue;
av_packet_unref(pkt);
}
av_packet_unref(pkt);//用完packet第一时间释放内存
/*
* frame的接收需要注意,一般一个pkt可以解码出一帧视频或者多帧音频,
* 所以对于音频我们应该多次receive,保证读取完毕。
*/
/*音频帧*/
if (isAudio)
{
while (avcodec_receive_frame(actx, frame)==0)
{
ret=swr_convert(au_convert_ctx, &outBuff, MAX_AUDIO_FRAME_SIZE, (const uint8_t**)frame->data, frame->nb_samples);
if (ret < 0)
{
printf("swr_convert failed!\n");
}
while (audioLen > 0)
SDL_Delay(1);
//printf(" audio avcodec_receive_frame successed!\n");
audioChunk = (unsigned char*)outBuff;
audioPos = audioChunk;
audioLen = out_buffer_size;
Synpts = frame->pts;
av_frame_unref(frame);
}
}
/*视频帧*/
else
{
ret = avcodec_receive_frame(vctx, frame);
if (ret < 0)
{
printf(" video avcodec_receive_frame failed!\n");
av_frame_unref(frame);
continue;
}
/*
* SDL
*/
pts = frame->pts;
SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
SDL_RenderClear(sdlRenderer);
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
SDL_RenderPresent(sdlRenderer);
//SDL_Delay(40);
//释放内存
av_frame_unref(frame);
/*
* SDL End
*/
//Sleep(500);//40ms
continue;
}
}
av_packet_free(&pkt);
av_frame_free(&frame);
//释放内存
avformat_free_context(ifmt);
avcodec_parameters_free(&vpara);
avcodec_free_context(&vctx);
SDL_Quit();
return 0;
}
【1】csdn 工程下载——vs打开直接即可运行