1.简介
这里引入FFmpeg库,获取视频流数据,然后通过FFmpeg将视频流解码成YUV原始数据,再将YUV数据送入到SDL库中实现视频播放。
2.FFmpeg的操作流程
- 注册API:av_register_all()
- 构建输入AVFormatContext上下文:avformat_open_input()
- 查找音视频流信息:avformat_find_stream_info()
- 查找解码器:avcodec_find_decoder()
- 打开解码器:avcodec_open2()
- 然后通过while循环,不停的读取数据:av_read_frame()
- 帧解码:avcodec_send_packet()和avcodec_receive_frame()
3.SDL显示视频流程
- 初始化SDL:SDL_Init();
- 创建窗口:SDL_CreateWindow();
- 创建渲染器:SDL_CreateRenderer();
- 创建纹理:SDL_CreateTexture();
- 设置纹理数据:SDL_UpdateTexture();
- 纹理复制给渲染目标:使用SDL_RenderCopy()将纹理数据复制给渲染目标。在使用SDL_RenderCopy()之前,可以使用SDL_RenderClear()先使用清空渲染目标。
- 显示界面:SDL_RenderPresent();
4.示例
#include <stdio.h>
#include <SDL.h>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
};
#define __STDC_CONSTANT_MACROS
//Refresh Event
#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)
#define SFM_BREAK_EVENT (SDL_USEREVENT + 2)
int thread_exit = 0;
int thread_pause = 0;
int sfp_refresh_thread(void *opaque)
{
while (!thread_exit)
{
if (!thread_pause)
{
SDL_Event event;
event.type = SFM_REFRESH_EVENT;
SDL_PushEvent(&event);
}
SDL_Delay(40);
}
//Break
SDL_Event event;
event.type = SFM_BREAK_EVENT;
SDL_PushEvent(&event);
return 0;
}
AVFrame *recv(AVCodecContext *codecCtx)
{
if (!codecCtx)
{
return NULL;
}
AVFrame *frame = av_frame_alloc();
int ret = avcodec_receive_frame(codecCtx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
av_frame_free(&frame);
return NULL;
}
else if (ret < 0)
{
av_frame_free(&frame);
return NULL;
}
return frame;
}
#undef main
int main(int argc, char* argv[])
{
///ffmpeg
avformat_network_init();
AVFormatContext* pFormatCtx = NULL;
const char* inputUrl = "./3.mp4";
///打开输入的流
int ret = avformat_open_input(&pFormatCtx, inputUrl, NULL, NULL);
if (ret != 0)
{
printf("Couldn't open input stream.\n");
return -1;
}
//查找流信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
{
printf("Couldn't find stream information.\n");
return -1;
}
//找到视频流索引
int video_index = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
AVStream* st = pFormatCtx->streams[video_index];
AVCodec* codec = nullptr;
//找到解码器
codec = avcodec_find_decoder(st->codecpar->codec_id);
if (!codec)
{
fprintf(stderr, "Codec not found\n");
return -1;
}
//申请AVCodecContext
AVCodecContext* pCodecCtx = avcodec_alloc_context3(codec);
if (!pCodecCtx)
{
return -1;
}
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[video_index]->codecpar);
//打开解码器
if ((ret = avcodec_open2(pCodecCtx, codec, NULL) < 0))
{
return -1;
}
AVFrame *pFrameYUV = av_frame_alloc();
AVPacket* pkt = av_packet_alloc();
unsigned char *out_buffer = (unsigned char *)av_malloc(av_image_get_buffer_size(
AV_PIX_FMT_YUV420P,
pCodecCtx->width,
pCodecCtx->height,
1)
);
av_image_fill_arrays(pFrameYUV->data,
pFrameYUV->linesize,
out_buffer,
AV_PIX_FMT_YUV420P,
pCodecCtx->width,
pCodecCtx->height,
1
);
//------------SDL----------------
int screen_w, screen_h;
SDL_Rect sdlRect;
struct SwsContext *img_convert_ctx;
char filepath[] = "Titanic.ts";
//Output Info-----------------------------
printf("---------------- File Information ---------------\n");
av_dump_format(pFormatCtx, 0, filepath, 0);
printf("-------------------------------------------------\n");
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
printf("Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}
//SDL 2.0 Support for multiple windows
screen_w = pCodecCtx->width;
screen_h = pCodecCtx->height;
SDL_Window *screen = SDL_CreateWindow("SDL Play Video", 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;
}
SDL_Renderer *sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
//IYUV: Y + U + V (3 planes)
//YV12: Y + V + U (3 planes)
SDL_Texture *sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);
sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = screen_w;
sdlRect.h = screen_h;
SDL_Thread *video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);
//------------SDL End------------
//Event Loop
SDL_Event event;
for (;;)
{
//Wait
SDL_WaitEvent(&event);
if (event.type == SFM_REFRESH_EVENT)
{
//不断的读取一帧数据
while (1)
{
if (av_read_frame(pFormatCtx, pkt) < 0)
thread_exit = 1;
if (pkt->stream_index == video_index)
break;
}
//一次send 多次recv
int ret = avcodec_send_packet(pCodecCtx, pkt);
if (ret < 0)
continue;
//释放资源
av_packet_unref(pkt);
while (1)
{
AVFrame *pFrame = recv(pCodecCtx);
if (!pFrame)
break;
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0,
pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
//SDL---------------------------
SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
SDL_RenderClear(sdlRenderer);
//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
SDL_RenderPresent(sdlRenderer);
//SDL End-----------------------
av_frame_free(&pFrame);
}
}
else if (event.type == SDL_KEYDOWN)
{
//空格键暂停播放
if (event.key.keysym.sym == SDLK_SPACE)
thread_pause = !thread_pause;
}
else if (event.type == SDL_QUIT)
{
thread_exit = 1;
}
else if (event.type == SFM_BREAK_EVENT)
{
break;
}
}
SDL_Quit();
//--------------
sws_freeContext(img_convert_ctx);
avcodec_close(pCodecCtx);
avcodec_free_context(&pCodecCtx);
avformat_close_input(&pFormatCtx);
av_frame_free(&pFrameYUV);
av_packet_free(&pkt);
return 0;
}
5.相关推荐
[总结]FFMPEG视音频编解码零基础学习方法_零基础ffmpeg 雷霄骅-CSDN博客
SDL2 播放视频数据(YUV420P)-CSDN博客
SDL2 消息循环和事件响应-CSDN博客
FFmpeg 视频解码(秒懂)-CSDN博客