可以拖动屏幕的简单页面播放示例
#include <iostream>
#ifdef __cplusplus ///
extern "C"
{
// 包含ffmpeg头文件
#include "libavutil/avutil.h"
#include"libavformat/avformat.h"
// 包含SDL头文件
#include"SDL.h"
}
#endif
using namespace std;
//Refresh--自定义事件
#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)
// 线程停止运行标识,0为正在运行,1为停止
int thread_exit = 0;
// 输出错误信息
void showError(int ret, const char *methodName = "method")
{
if(ret == 0) {
return ;
}
// 错误消息日志
char err2str[256];
// 将返回结果转化为字符串信息
av_strerror(ret, err2str, sizeof(err2str));
printf("%s failed, ret:%d, msg:%s\n", methodName, ret, err2str);
}
// 刷新页面显示的线程回调函数
int refresh_thread(void *opaque)
{
// 定义一个SDL事件
SDL_Event event;
// 线程执行中
while(thread_exit == 0)
{
// 设置该事件的类型
event.type = SFM_REFRESH_EVENT;
// 向SDL发送事件
SDL_PushEvent(&event);
// 等待23ms【这样每隔23ms就会发送一次SDL事件,类型是刷新屏幕,这样就算拖动屏幕了【拖动屏幕这个事件导致刷新屏幕不运行了】
// ,在23ms后又会收到SDL事件通知要刷新屏幕,在主线程中就又会刷新屏幕了】
SDL_Delay(13);
}
return 0;
}
#undef main
int main(int argc, char *argv[])
{
if(argc < 2)
{
cout << "请输入视频地址" << endl;
return -1;
}
// 获取视频地址
char *url = argv[1];
cout << url << endl;
// 方法调用结果
int ret = 0;
// FFmpeg
// AVFormatContext 是音视频开发使用到最多的结构体,无论什么函数基本上都会用到它
// AVFormatContext 只能通过 avformat_alloc_context() 创建空的对象
AVFormatContext *input_fmt_ctx = avformat_alloc_context();
// 加载视频内容到音视频格式上下文中
ret = avformat_open_input(&input_fmt_ctx, url, NULL, NULL);
// 输出日志
showError(ret);
// 查看流信息,可以不写,只是单纯拿返回值来做校验的
ret = avformat_find_stream_info(input_fmt_ctx, NULL);
// 输出日志
showError(ret);
// 输出视频信息,可以不写
av_dump_format(input_fmt_ctx, 0, url, 0);
// 查找指定流的idx,如果使用不到,可以不写; AVMEDIA_TYPE_VIDEO 代表视频流,AVMEDIA_TYPE_AUDIO代表音频流
int video_idx = av_find_best_stream(input_fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
int audio_idx = av_find_best_stream(input_fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
printf("video_idx: %d , audio_idx: %d\n", video_idx, audio_idx);
// AVCodecContext 是解码器上下文,需要对帧处理基本上都会用到它
// AVCodecContext 只能通过 avcodec_alloc_context3(NULL) 创建空的对象
AVCodecContext *codec_ctx = avcodec_alloc_context3(NULL);
// 将音视频格式上下文中的参数加载到解码器上下文对象中
ret = avcodec_parameters_to_context(codec_ctx, input_fmt_ctx->streams[video_idx]->codecpar);
// 输出日志
showError(ret);
// 指定物理解码器;这里参数传的是codec_ctx->codec_id,实际上物理解码器有很多中,这里可以传不同的内容
AVCodec *codec = avcodec_find_decoder(codec_ctx->codec_id);
// 将物理解码器加载到解码器上下文中
ret = avcodec_open2(codec_ctx, codec, NULL);
// 输出日志
showError(ret);
// 包,用来获取音视频格式上下文中的数据
// AVPacket 只能通过 av_packet_alloc() 创建对象
AVPacket *pkt = av_packet_alloc();
// 帧,用来获取包解码后的数据
// AVFrame 只能通过 av_frame_alloc() 创建对象
AVFrame *frame = av_frame_alloc();
// SDL
// 初始化视频
if(SDL_Init(SDL_INIT_VIDEO)) {
return -1;
}
// 视频宽度
int video_width_ = input_fmt_ctx->streams[video_idx]->codecpar->width;
// 视频高度
int video_height_ = input_fmt_ctx->streams[video_idx]->codecpar->height;
// 创建窗口--显示器
// 在这里设置显示出来的窗口的总大小
SDL_Window *win_ = SDL_CreateWindow("苏花末测试窗口", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
video_width_, video_height_, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
if(!win_) {
return -1;
}
// 渲染器,用于将纹理渲染到窗口上
SDL_Renderer *renderer_ = SDL_CreateRenderer(win_, -1, 0);
if(!renderer_) {
return -1;
}
// 纹理,用于设置渲染图片数据
SDL_Texture *texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, video_width_, video_height_);
if(!texture_) {
return -1;
}
// Rect--页面显示区域
SDL_Rect rect_;
// 创建一个线程用于SDL接受事件
SDL_CreateThread(refresh_thread, NULL, NULL);
// SDL事件
SDL_Event event;
// output and readFrame
while(1)
{
// 等待SDL获取到事件【阻塞等待】
SDL_WaitEvent(&event);
// 只有事件类型是刷新时才继续执行
if(event.type != SFM_REFRESH_EVENT)
{
continue;
}
// FFmpeg: readFrame
// 获取该音视频格式上下文中的第一个包,并将从音视频格式上下文中移除
// 则代表了每次调用都会获取到新的包,之前的包不会再在该音视频格式上下文中找到了
ret = av_read_frame(input_fmt_ctx, pkt);
// 输出日志
showError(ret);
// 如果包数据读取完毕,则代表视频播放结束了
if(ret < 0)
{
cout << "play video finish" << endl;
break;
}
// 本次演示只展示视频播放,故跳过音频帧
if(pkt->stream_index == audio_idx)
{
continue;
}
// 将包加载到解码器上下文中进行解码
ret = avcodec_send_packet(codec_ctx, pkt);
// 输出日志
showError(ret);
// 读取解码后的包中的帧
ret = avcodec_receive_frame(codec_ctx, frame);
// 如果 AVERROR(EAGAIN) == ret,则代表这个包无法解析,需要再次解析下一个包
if(AVERROR(EAGAIN) == ret)
{
continue;
}
// 输出日志
showError(ret);
// SDL: output
// 清空之前的页面
SDL_RenderClear(renderer_);
// 设置rect所占区域
rect_.x = 0;
rect_.y = 0;
// 在这里设置rect区域的大小,如果这里和窗口总大小不一样,那么其他地方是黑屏显示
// 故这里也体现了一个win可以设置多个rect,每个rect可以占据不同的位置
rect_.w = video_width_;
rect_.h = video_height_;
// 通过YUV格式渲染图片
SDL_UpdateYUVTexture(texture_, &rect_,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
// 页面内容设置
SDL_RenderCopy(renderer_, texture_, NULL, &rect_);
// 显示新的页面
SDL_RenderPresent(renderer_);
}
system("pause");
return 0;
}