可以拖动屏幕的简单页面播放示例

#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; }
上一篇:vue3 + ts + element-plus 二次封装 el-dialog


下一篇:NGINX 保护 Web 应用安全之基于 IP 地址的访问