AVFormatContext *pFormatCtx{};
AVCodecParameters *pCodeParameters{};
const AVCodec *pCodec {};
AVCodecContext *pCodecCtx {};
AVFrame *pFrame {};
AVPacket packet {};
SDL_Rect rect;
SDL_Window *window{};
SDL_Renderer *renderer{};
SDL_Texture *texture{};
Uint32 pixformat;
SDL_Thread *timer_thread{};
SDL_Event event;
// 视频分辨率
int vWidth = s_vWidth;
int vHeight = s_vHeight;
// 窗口的分辨率
int sdlWidth = s_vWidth;
int sdlHeight = s_vHeight;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
cout << "initialize SDL failed" << endl;
return ret;
}
if (avformat_open_input(&pFormatCtx, file, 0, nullptr) != 0) {
cout << "open the video file failed!" << endl;
return 0;
}
videostream = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1,
nullptr, 0);
if (videostream == -1) {
cout << "open a video stream failed!" << endl;
return 0;
}
pCodec = avcodec_find_decoder(
pFormatCtx->streams[videostream]->codecpar->codec_id);
if (pCodec == nullptr) {
cout << "find a codec failed!" << endl;
return 0;
}
pCodecCtx = avcodec_alloc_context3(pCodec);
if (avcodec_parameters_to_context(pCodecCtx,
pFormatCtx->streams[videostream]->codecpar) != 0) {
cout << "copy codec context failed!" << endl;
return 0;
}
if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0) {
cout << "open the decoder failed!" << endl;
return 0;
}
pFrame = av_frame_alloc();
// 获得显示的视频画面的长度与宽度
vWidth = pCodecCtx->width;
vHeight = pCodecCtx->height;
// 创建窗口
window = SDL_CreateWindow("SDL PLAYER",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
vWidth, vHeight,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
if (!window) {
cout << "create window failed!" << endl;
return 0;
}
// 创建render
renderer = SDL_CreateRenderer(window, -1, 0);
if (!renderer) {
cout << "create renderer failed!" << endl;
return 0;
}
// 视频格式设置为SDL_PIXELFORMAT_IYUV
pixformat = SDL_PIXELFORMAT_IYUV;
// 创建纹理
texture = SDL_CreateTexture(renderer,
pixformat,
SDL_TEXTUREACCESS_STREAMING,
vWidth,
vHeight);
// 创建SDL线程
timer_thread = SDL_CreateThread(refresh_video_timer, nullptr, nullptr);
// 主循环
while (1) {
// 无限期等待下一个可用事件
SDL_WaitEvent(&event);
if (event.type == REFRESH_EVENT) {// 画面刷新事件
// 每次只需要读取一个packet
while (1) {
// 如果已经读取完所有的帧,则退出
if (av_read_frame(pFormatCtx, &packet) < 0) {
s_thread_exit = 1;
}
// 读取的是视频帧就退出
if (packet.stream_index == videostream) {
break;
}
}
// 将一个packet进行解码操作
avcodec_send_packet(pCodecCtx, &packet);
// 将frame进行读取
while (avcodec_receive_frame(pCodecCtx, pFrame) == 0) {
// 刷新纹理
SDL_UpdateYUVTexture(texture, NULL,
pFrame->data[0], pFrame->linesize[0],
pFrame->data[1], pFrame->linesize[1],
pFrame->data[2], pFrame->linesize[2]);
// 设置渲染目标的显示区域
rect.x = 0;
rect.y = 0;
rect.w = pCodecCtx->width;
rect.h = pCodecCtx->height;
// 清空渲染器内容
SDL_RenderClear(renderer);
// 将纹理复制到渲染器
SDL_RenderCopy(renderer, texture, NULL, &rect);
// 渲染
SDL_RenderPresent(renderer);
}
// 将packet内容清空
av_packet_unref(&packet);
} else if (event.type == SDL_WINDOWEVENT) { // 如果窗口出现了调整
SDL_GetWindowSize(window, &sdlWidth, &sdlHeight);
} else if (event.type == SDL_QUIT) { // 如果窗口被关闭
s_thread_exit = 1;// 退出画面刷新线程
} else if (event.type == QUIT_EVENT) { // 退出主线程循环
break;
}
}
// 释放所有内存
if (pFrame) {
av_frame_free(&pFrame);
pFrame = nullptr;
}
if (pCodecCtx) {
avcodec_close(pCodecCtx);
pCodecCtx = nullptr;
pCodec = nullptr;
}
if (pFormatCtx) {
avformat_close_input(&pFormatCtx);
pFormatCtx = nullptr;
}
if (pCodeParameters) {
avcodec_parameters_free(&pCodeParameters);
pCodeParameters = nullptr;
}
if (texture) {
SDL_DestroyTexture(texture);
texture = nullptr;
}
if (renderer) {
SDL_DestroyRenderer(renderer);
renderer = nullptr;
}
if (window) {
SDL_DestroyWindow(window);
window = nullptr;
}
SDL_Quit();
return ret;
// 参数解析:
/**
* @param ps 向用户提供的AVFormatContext的指针(avformat_alloc_context分配)。可能是
* NULL指针,这种情况下,此函数分配AVFormatContext并将其写入ps。
*
* 请注意,用户提供的AVFormatContext将在出现故障时释放。
* @param url 要打开的流的URL
* @param fmt 如果非空,则此参数强制使用特定的输入格式。否则,将自动检测格式。
*
* @param options 充满AVFormatContext和解复用器专用选项的字典。返回时,此参数将被销毁并替换未包含
* 未找到选项的dict。可以为空。
*
* @return 成功时为0,失败时为负平均值。
*
* @note 如果要使用自定义IO,请预先分配格式上下文并设置其pb字段。
*/
// 参数解析
/**
* @param ic 媒体文件句柄
* @param type 流类型:视频、音频、字幕等。
* @param wanted_stream_nb 用户请求的流号,或-1用于自动选择
* @param related_stream 尝试查找与此流相关的流(例如,在同一程序中),如果没有,则为-1
* @param decoder_ret 如果非空,则返回所选流的解码器
* @param flags 标识;当前未定义任何
* @return 成功情况下的非负流号,AVERROR_STREAM_NOT_FOUND如果找不到具有请求类型的流AVERROR_DECODER_NOT_FOUND如果找到流但没有解码器
* @note 如果av_find_best_stream成功返回并且decoder_ret不是NULL,则*decoder_ret保证设置为有效的AVCodec
*/
// 参数解析
/**
* @param id 所请求解码器的AVCodecID
* @return 找到返回一个解码器,否则返回NULL
*/
// 参数解析
/**
* @param codec 如果非空,则为给定编解码器分配私有数据并初始化默认值。然后使用不同的编解码器调用avcodec_open2()是非法的。
* 如果为空,则特定于编解码器的默认值将不会初始化,这可能会导致次优的默认设置(这主要对编码器很重要,例如libx264)。
*
* @return 一个填充了默认值的AVCodecContext或者失败时为空
*/
// 参数解析
/**
* @return 成功时返回>=0,失败时为负平均错误代码
*/
// 参数解析
/**
* 函数avcodec_find_decoder_by_name(), avcodec_find_encoder_by_name(), 函数
* avcodec_find_decoder() and avcodec_find_encoder()提供了检索编解码器的简单方法
*
* @warning 此函数不是线程安全的
*
* @note 在使用解码例程(如avcodec_receive_frame())之前,始终调用此函数
*
* @code
* av_dict_set(&opts, "b", "2.5M", 0);
* codec = avcodec_find_decoder(AV_CODEC_ID_H264);
* if (!codec)
* exit(1);
*
* context = avcodec_alloc_context3(codec);
*
* if (avcodec_open2(context, codec, opts) < 0)
* exit(1);
* @endcode
*
* @param avctx 要初始化的上下文
* @param codec 打开此上下文的编解码器。如果先前已将非空编解码器传递给avcodec_alloc_context3(),
* 或对于此上下文,则此参数必须为空或等于先前传递的编解码器。
* @param options 一个包含AVCodecContext和编解码器专用选项的字典。返回时,此对象将填充未找到的选项。
*
* @return 成功时为0,错误时为负值。
* @see avcodec_alloc_context3(), avcodec_find_decoder(), avcodec_find_encoder(),
* av_dict_set(), av_opt_find().
*/
// 参数解析
/**
* @return 一个填充了默认数据的AVFrame或失败了返回NULL
*
* @note 这仅分配AVFrame本身,而不是数据缓冲区。必须通过其他方式进行分配,例如使用av_frame_get_buffer()或手动分配。
*/
项目下载链接
【1】100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)
【2】ffmpeg官方文档