视频播放器-FFMPEG官方库,包含lib,include,bin x64和x86平台的所有文件,提取码4v2c
视频播放器-LQVideo实现视频解码C++源代码,提取码br9u
视频播放器-SoundTouch实现声音变速的C++源代码,提取码6htk
本文主要是根据项目需求实现视频解码的底层封装,最终导出的是dll文件,供unity3d插件调用,本文主要分为三部分
- 环境配置
- 根据需求设计接口,实现接口
- 需要注意的问题
首先必须声明,第一部分和第二部分接口的实现我都是站在前人的肩膀上做的。为了功能的完整性,我会进行介绍,但是版权不是我的哦。
参考文章:
https://blog.csdn.net/leixiaohua1020/article/details/15811977
https://blog.csdn.net/leixiaohua1020/article/details/8652605
环境配置
我们使用FFMPEG库,首先要获取FFMPEG库的lib文件,bin文件和include文件
1. 访问https://ffmpeg.zeranoe.com/builds/
2. 选择好版本(一般选最新的就可以了)和系统架构,选择Dev后下载,里面包含lib文件和include文件
3. 选择Shared后下载,里面包含bin文件,下载完基本是下图这样的,每个文件夹都包含x86和x64两个文件夹
4. 创建C++工程,我使用的是VS2017,创建类库程序或者控制台程序都可以,创建完成后,右键项目,选择属性,需要配置的项如下图所示
- 第一项选择Debug或者Release是需要分别配置3,4,5的
- 第二项选择x64和x86在配置的时候需要记得找对应的文件夹
- 3的附加包含目录选择FFMPEG include文件夹下对应平台文件夹
- 4的附加库目录选择FFMPEG lib文件夹下对应平台文件夹
- 5的附加依赖项添加avcodec.lib;avformat.lib;avutil.lib;avdevice.lib;avfilter.lib;postproc.lib;swresample.lib;swscale.lib这几项
5. 这样环境就配置完成了,有一个需要注意的地方:C/C++->高级->禁用特定警告添加4996,不然会显示有很多不符合要求的API
接口设计及实现
既然环境配置完成了,接下来就需要设计我们的接口以及实现接口,在这一步之前,我需要先给定几个说明和假设
- 我们的接口是给Unity3d插件调用的,目前unity3d调用C++接口有两种方式,通过DllImport调用(Native调用)和通过引用调用(Managed调用),虽然Managed调用方便跨平台,但是由于unity3d规定,Managed调用需要使用托管C++并且使用的clr运行时必须是安全的,这样FFMPEG底层的库就都不能用了,所以我们采用Native调用。既然这样,C++接口的定义方式必须是如下的形式:
extern "C" _declspec(dllexport) int init_ffmpeg(char* url); - 强烈建议读一下开始给出的雷神写的两篇博客,至少了解一下视频解码的过程,我下面给出的详细的过程但是没有说明:
注册组件->解封装视频获取上下文->获取流信息->查找音频视频解码器->打开音频视频解码器->获取Packet包->解码Packet获取视频帧或音频帧->数据格式转换
了解了以上两点,我们接下来设计接口,根据需求我们设计如下接口,如果不同项目有不同的需求,可以自己适当增删:
- int init_ffmpeg(char* url):初始化视频信息,参数是视频路径,可以是本地路径或者网络路径,返回值是当前视频的编号,因为我们需要支持播放多个视频,如果返回值为-1,表示初始化失败
- int get_video_width(int key):获取视频宽度,参数代表视频编号,下同
- int get_video_height(int key):获取视频高度
- int get_video_length(int key):获取视频长度
- double get_video_frameRate(int key):获取视频帧速
- double get_current_time(int key):获取视频当前帧时间
- int get_audio_sample_rate(int key):获取音频采样率
- int get_audio_channel(int key):获取音频声道数
- double get_audio_time(int key):获取音频当前时间
- int read_video_frame(int key):读取一帧视频
- int get_video_buffer_size(int key):获取视频帧数据长度
- char *get_video_frame(int key):返回读取的视频帧数据
- int read_audio_frame(int key):读取一帧音频
- int get_audio_buffer_size(int key):获取音频帧数据长度
- char *get_audio_frame(int key):返回读取的音频帧数据
- void set_audio_disabled(int key, bool disabled):是否禁止使用音频,如果需求不需要声音,可以设置不使用音频,能节省性能
- void release(int key):释放视频数据,释放后当前key值可以被后面的视频使用
- int read_frame_packet(int key):从数据流中读取视频Packet,这个本来应该在C++底层做,起一个线程单独读取Packet包,但是线程关闭之后总莫名其妙的报内存冲突,所以我把这个线程拿到Unity3d中了,才添加的这个接口
- int get_first_video_frame(int key):读取第一帧数据,如果视频等待第一帧的话,需要预先加载第一帧
- bool seek_video(int key,int time):视频跳转功能
好了,以上就是我们使用的接口,先提供头文件的代码
#pragma once #include "AVPacketArray.h" #include "VideoState.h" #include <thread> #include <iostream> using namespace std; extern "C" _declspec(dllexport) int init_ffmpeg(char* url); //读取一帧 extern "C" _declspec(dllexport) int read_video_frame(int key); extern "C" _declspec(dllexport) int read_audio_frame(int key); extern "C" _declspec(dllexport) char *get_audio_frame(int key); //获取视频帧 extern "C" _declspec(dllexport) char *get_video_frame(int key); extern "C" _declspec(dllexport) void set_audio_disabled(int key, bool disabled); //获取视频缓存大小 extern "C" _declspec(dllexport) int get_video_buffer_size(int key); extern "C" _declspec(dllexport) int get_audio_buffer_size(int key); //获取视频宽度 extern "C" _declspec(dllexport) int get_video_width(int key); //获取视频高度 extern "C" _declspec(dllexport) int get_video_height(int key); extern "C" _declspec(dllexport) int get_video_length(int key); extern "C" _declspec(dllexport) double get_video_frameRate(int key); extern "C" _declspec(dllexport) bool seek_video(int key,int time); extern "C" _declspec(dllexport) int get_audio_sample_rate(int key); extern "C" _declspec(dllexport) int get_audio_channel(int key); extern "C" _declspec(dllexport) double get_current_time(int key); extern "C" _declspec(dllexport) double get_audio_time(int key); extern "C" _declspec(dllexport) int get_version(); //释放资源 extern "C" _declspec(dllexport) void release(int key); extern "C" _declspec(dllexport) int read_frame_packet(int key); extern "C" _declspec(dllexport) int get_first_video_frame(int key);
接下来我们需要提供两个数据结构,一个用于存储视频的所有数据的类,一个环形队列的算法类,我需要说明一下,这个封装的dll一共就三个头文件,三个cpp文件,所以介绍到这里基本就剩接口的实现了,文章最后我会给出完整代码的链接。
视频数据类:VideoState.h
#pragma once #include "AVPacketArray.h" #include <mutex> using namespace std; class VideoState { public: // 是否被使用 bool isUsed = false; // 视频解封装上下文 AVFormatContext *fmt_ctx = nullptr; // 流队列中,视频流所在的位置 int video_index = -1; //流队列中,音频流所在的位置 int audio_index = -1; //视频解码上下文 AVCodecContext *video_codec_ctx = nullptr; //音频解码上下文 AVCodecContext *audio_codec_ctx = nullptr; // 视频输出缓存大小 int video_out_buffer_size = 0; // 音频输出缓存大小 int audio_out_buffer_size = 0; // 视频输出缓存 uint8_t *video_out_buffer = nullptr; // 音频输出缓存 uint8_t *audio_out_buffer = nullptr; //转码后输出的视频帧(如yuv转rgb24) AVFrame *video_out_frame = nullptr; //从Packet中获取的帧信息 AVFrame *original_audio_frame = nullptr; //从Packet中获取的帧信息 AVFrame *original_video_frame = nullptr; //视频格式转换上下文 struct SwsContext *video_convert_ctx = nullptr; // 音频格式转换上下文 struct SwrContext *audio_convert_ctx = nullptr; //解码前数据包 AVPacket *packet = nullptr; //音频声道数量 int nb_channels = 2; //输出的采样格式 16bit PCM enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16; //采样率 int sample_rate = 44100; //输出的声道布局:立体声 uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO; // 当前播放时间 double playTime = 0; //音频的时间点 double audioTime = 0; //是否正在跳转 bool isSeeking = false; // 数据包是否读取到文件结尾 bool isEnd = false; //视频Packet缓存数据包队列 AVPacketArray videoPacketVector; //音频Packet缓存数据包队列 AVPacketArray audioPacketVector; //同步锁 mutex lockObj; //是否禁用声音 bool audioDisabled = false; VideoState() :isUsed(false), out_sample_fmt(AV_SAMPLE_FMT_S16) , out_ch_layout(AV_CH_LAYOUT_STEREO), audio_index(-1), video_index(-1) , video_out_buffer_size(0), audio_out_buffer_size(0), nb_channels(2), sample_rate(44100) , playTime(0), audioTime(0), isSeeking(false), isEnd(false) { } void Release(); ~VideoState() { Release(); } };
视频数据类:VideoState.cpp
#include "VideoState.h" void VideoState::Release() { if (!isUsed) { return; } av_free(video_out_buffer); av_free(audio_out_buffer); av_frame_free(&(video_out_frame)); av_frame_free(&(original_audio_frame)); av_frame_free(&(original_video_frame)); av_free_packet(packet); sws_freeContext(video_convert_ctx); swr_free(&(audio_convert_ctx)); avcodec_close(video_codec_ctx); avcodec_close(audio_codec_ctx); avformat_close_input(&(fmt_ctx)); video_out_buffer = nullptr; audio_out_buffer = nullptr; video_out_frame = nullptr; packet = nullptr; video_convert_ctx = nullptr; audio_convert_ctx = nullptr; video_codec_ctx = nullptr; audio_codec_ctx = nullptr; fmt_ctx = nullptr; videoPacketVector.clear(); audioPacketVector.clear(); isEnd = false; audioDisabled = false; isUsed = false; }
环形队列类 AVPacketArray.h
#pragma once extern "C" { #include "libavformat/avformat.h" #include "libswscale/swscale.h" #include "libavutil/imgutils.h" #include "libswresample/swresample.h" } class AVPacketArray { private: const static int max = 300; int pos = 0; int end = 0; int grade = 0; AVPacket data[max]; public: AVPacketArray(); void push(AVPacket& value); AVPacket& pop(); void clear(); inline int size() { int ret = end + grade * max - pos; return ret > 0 ? ret : 0; } ~AVPacketArray(); };
环形队列类 AVPacketArray.cpp
#include "AVPacketArray.h" AVPacketArray::AVPacketArray() { } void AVPacketArray::push(AVPacket& value) { if (end < max-1) { data[end++] = value; } else if (end==max-1) { data[end] = value; end = 0; grade = 1; } } AVPacket& AVPacketArray::pop() { int ret = pos; if (pos == max - 1 && grade == 1) { pos = 0; grade = 0; } else { pos++; } return data[ret]; } void AVPacketArray::clear() { if (grade == 0) { for (int index = pos; index < end; index++) { av_free_packet(&data[index]); } } else { for (int index = pos; index < max; index++) { av_free_packet(&data[index]); } for (int index = 0; index < end; index++) { av_free_packet(&data[index]); } } grade = 0; pos = 0; end = 0; } AVPacketArray::~AVPacketArray() { }
是不是很简单
接下来就剩接口的实现类了,因为代码有点多,我把代码放到最后,通过这篇文章,我所期望达到的目的就是
- 按照配置步骤配置环境
- 将所有的代码拷贝到代码文件中
- 编译,通过
- 可以创建控制台项目,在main函数中调用接口,我之前代码是有的,方便测试,后来封版后删掉了
需要注意的问题
- 视频跳转的时候,会跳转到左侧最近的关键帧,因为没有关键帧后面的图像是渲染不出来的,所以在视频跳转的实现中,跳转完成会启动一个线程去向后解码,一直到解码的时间正好是跳转的时间为止,这个过程中是不会播放的,有isSeeking开关控制。
- 虽然我去掉了,但是必须在视频读帧之前启动线程,来预加载Packet包,这个是很有必要的,不仅仅是加快解码的过程,更重要的是将视频包和音频包分开。我是预加载了50个,这个线程在视频播放过程中是一直运行的。
- 因为涉及环形队列的push和pop操作,所以代码中很多地方使用了锁,底层的锁是没问题的,但是如果在上层也使用了锁的话,一定要做好防止死锁的准备。
- 最最最重要的,使用FFMPEG的接口分配的内存一定要用专门的接口释放,并且一定要记得释放,不然出了问题查bug实在是太头疼了。
接口的实现代码LQVideo.cpp
#include "LQVideo.h" //支持的最大的视频同播数量 const static int maxVideoCount = 10; //当前视频组件支持同时播放三个视频 static VideoState states[maxVideoCount]; // 当前有效的视频播放位置,取值范围为0~maxVideoCount-1 static int pos = -1; //从视频资源中读取Packet信息到缓存,该信息是解码之前的信息,数据比较小 int read_frame_packet(int key) { if (key >= maxVideoCount) { return -3; } if (states[key].isSeeking) { return -2; } if (states[key].videoPacketVector.size() >= 50 || states[key].isEnd) { return -1; } lock_guard<mutex> lock_guard(states[key].lockObj); int index = 0; while (index < 10) { AVPacket* packet = av_packet_alloc(); int ret = av_read_frame(states[key].fmt_ctx, packet); // 到达视频结尾了 if (ret == AVERROR_EOF) { states[key].isEnd = true; av_free_packet(packet); break; } if (ret != 0 || (packet->stream_index == states[key].audio_index && states[key].audioDisabled)) { av_free_packet(packet); } else if (packet->stream_index == states[key].video_index) { states[key].videoPacketVector.push(*packet); index++; } else if (packet->stream_index == states[key].audio_index && !states[key].audioDisabled) { states[key].audioPacketVector.push(*packet); } } return 0; } //初始化FFmpeg //@param *url 媒体地址(本地/网络地址) int init_ffmpeg(char *url) { try { av_register_all(); //注册组件 avformat_network_init(); //支持网络流 for (int index = 0; index < maxVideoCount; index++) { if (!states[index].isUsed) { pos = index; states[pos].isUsed = true; break; } else if (index == maxVideoCount - 1) { // 所有视频数据通道都被占用,无法播放视频 return -1; } } // 分配视频format上下文内存并且返回指针 states[pos].fmt_ctx = avformat_alloc_context(); //打开文件 if (avformat_open_input(&states[pos].fmt_ctx, url, nullptr, nullptr) != 0) { return -1; } //查找流信息 if (avformat_find_stream_info(states[pos].fmt_ctx, nullptr) < 0) { return -1; } //找到流队列中,视频流所在位置 for (int i = 0; i < (states[pos].fmt_ctx->nb_streams); i++) { if (states[pos].fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { states[pos].video_index = i; } else if (states[pos].fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO&& states[pos].audio_index==-1) { states[pos].audio_index = i; } } //视频流没有找到 if (states[pos].video_index == -1) { return -1; } //查找视频解码器 states[pos].video_codec_ctx = states[pos].fmt_ctx->streams[states[pos].video_index]->codec; AVCodec *video_codec = avcodec_find_decoder(states[pos].video_codec_ctx->codec_id); //查找音频解码器 states[pos].audio_codec_ctx = states[pos].fmt_ctx->streams[states[pos].audio_index]->codec; AVCodec *audio_codec = avcodec_find_decoder(states[pos].audio_codec_ctx->codec_id); //解码器没有找到 if (video_codec == NULL) { return -1; } //打开视频解码器 if (avcodec_open2(states[pos].video_codec_ctx, video_codec, NULL) < 0) { return -1; } //打开音频解码器 if (audio_codec==NULL || avcodec_open2(states[pos].audio_codec_ctx, audio_codec, NULL) < 0) { states[pos].audioDisabled = true; } //计算视频帧输出缓存大小 states[pos].video_out_buffer_size = av_image_get_buffer_size(AV_PIX_FMT_RGB24, states[pos].video_codec_ctx->width, states[pos].video_codec_ctx->height,1); //分配视频帧内存 states[pos].video_out_frame = av_frame_alloc(); //分配视频帧输出数据内存 states[pos].video_out_buffer = (uint8_t*)av_malloc(states[pos].video_out_buffer_size); //准备一些参数,在视频格式转换后,参数将被设置值 av_image_fill_arrays(states[pos].video_out_frame->data, states[pos].video_out_frame->linesize, states[pos].video_out_buffer,AV_PIX_FMT_RGB24, states[pos].video_codec_ctx->width, states[pos].video_codec_ctx->height,1); //图片格式转换上下文 states[pos].video_convert_ctx = sws_getContext(states[pos].video_codec_ctx->width, states[pos].video_codec_ctx->height, states[pos].video_codec_ctx->pix_fmt, states[pos].video_codec_ctx->width, states[pos].video_codec_ctx->height,AV_PIX_FMT_RGB24,SWS_BICUBIC,NULL, NULL, NULL); //======音频转码准备======start====== //输入的采样格式 enum AVSampleFormat in_sample_fmt = states[pos].audio_codec_ctx->sample_fmt; //输入的采样率 states[pos].sample_rate = states[pos].audio_codec_ctx->sample_rate; //输入的声道布局 uint64_t in_ch_layout = states[pos].audio_codec_ctx->channel_layout; states[pos].audio_convert_ctx = swr_alloc(); swr_alloc_set_opts(states[pos].audio_convert_ctx, states[pos].out_ch_layout, states[pos].out_sample_fmt, states[pos].sample_rate, in_ch_layout, in_sample_fmt, states[pos].sample_rate, 0, NULL); swr_init(states[pos].audio_convert_ctx); //获取声道个数 states[pos].nb_channels = av_get_channel_layout_nb_channels(states[pos].out_ch_layout); //存储pcm数据 states[pos].audio_out_buffer = (uint8_t *)av_malloc(states[pos].sample_rate * 2); //======音频转码准备======end====== states[pos].packet = av_packet_alloc(); states[pos].original_audio_frame = av_frame_alloc(); states[pos].original_video_frame = av_frame_alloc(); } catch (const std::exception& ex) { return -1; } return pos; } //线程执行seek之后从关键帧到time帧的遍历 //@param key 当前视频的索引值,支持10个视频同时读取 //@param time 当前跳转的时间 void read_frame_thread(int key, int time) { int got_picture; int got_frame; double timeTemp; int read_frame_ret; int decode_ret; //从packet中解出来的原始视频帧 if (!states[key].isUsed) { return; } lock_guard<mutex> lock_guard(states[key].lockObj); while (true) { av_init_packet(states[key].packet); read_frame_ret = av_read_frame(states[key].fmt_ctx, states[key].packet); if (read_frame_ret != 0) { continue; } if (states[key].packet->stream_index == states[key].audio_index) { avcodec_decode_audio4(states[key].audio_codec_ctx, states[key].original_audio_frame, &got_frame, states[key].packet); } else if (states[key].packet->stream_index == states[key].video_index) { decode_ret = avcodec_decode_video2(states[key].video_codec_ctx, states[key].original_video_frame, &got_picture, states[key].packet); if (decode_ret >= 0 && got_picture) { timeTemp = (av_q2d(states[key].fmt_ctx->streams[states[key].video_index]->time_base)*(states[key].packet->dts)); if (timeTemp >= time) { break; } } } } states[key].videoPacketVector.clear(); states[key].audioPacketVector.clear(); states[key].isEnd = false; states[key].isSeeking = false; } //解码一帧视频信息 //@param key 当前视频的索引值,支持10个视频同时读取 int read_video_frame(int key) { int ret = -1; // 基本错误 if (key >= maxVideoCount) { return -1; } if (!states[key].isUsed) { return -1; } // 正在跳转 无法读取帧信息 if (states[key].isSeeking) { return -2; } // 如果缓存中没有数据,说明读取完成或者还没有加载 if (states[key].videoPacketVector.size() == 0) { return (states[key].isEnd) ? -3 : -1; } lock_guard<mutex> lock_guard(states[key].lockObj); //是否从packet中解出一帧,0为未解出 int got_picture; AVPacket& temp = states[key].videoPacketVector.pop(); //printf("读取视频帧\n"); //解码。输入为packet,输出为original_video_frame int decodeValue = avcodec_decode_video2(states[key].video_codec_ctx, states[key].original_video_frame, &got_picture, &temp); if (decodeValue > 0 && got_picture) { states[key].playTime = (av_q2d(states[key].fmt_ctx->streams[states[key].video_index]->time_base)*(temp.dts)); //printf("当前时间:%f\n", states[key].playTime); //图片格式转换(上面图片转换准备的参数,在这里使用) sws_scale(states[key].video_convert_ctx,(const uint8_t* const*)states[key].original_video_frame->data, states[key].original_video_frame->linesize,0,states[key].video_codec_ctx->height,states[key].video_out_frame->data,states[key].video_out_frame->linesize); ret = 2; } av_free_packet(&temp); return ret; } //解码一帧音频信息 //@param key 当前视频的索引值,支持10个视频同时读取 int read_audio_frame(int key) { int ret = -1; // 基本错误 if (key >= maxVideoCount) { return -1; } if (!states[key].isUsed || states[key].audioPacketVector.size() == 0) { return -1; } // 正在跳转 无法读取帧信息 if (states[key].isSeeking) { return -2; } lock_guard<mutex> lock_guard(states[key].lockObj); int got_frame; AVPacket& temp = states[key].audioPacketVector.pop(); int decodeValue = avcodec_decode_audio4(states[key].audio_codec_ctx, states[key].original_audio_frame, &got_frame, &temp); if (decodeValue >= 0 && got_frame) { states[key].audioTime = (av_q2d(states[key].fmt_ctx->streams[states[key].audio_index]->time_base)*(temp.dts)); //音频格式转换 swr_convert(states[key].audio_convert_ctx,&(states[key].audio_out_buffer),states[key].sample_rate * 2,(const uint8_t **)states[key].original_audio_frame->data, states[key].original_audio_frame->nb_samples); states[key].audio_out_buffer_size = av_samples_get_buffer_size(NULL, states[key].nb_channels, states[key].original_audio_frame->nb_samples, states[key].out_sample_fmt, 1); ret = 1; } av_free_packet(&temp); return ret; } //获取视频第一帧图像数据 int get_first_video_frame(int key) { while (true) { int ret = av_read_frame(states[key].fmt_ctx, states[key].packet); if (ret == AVERROR_EOF) { return -3; } if (states[key].packet->stream_index == states[key].video_index) { lock_guard<mutex> lock_guard(states[key].lockObj); //是否从packet中解出一帧,0为未解出 int got_picture; //解码。输入为packet,输出为original_video_frame int decodeValue = avcodec_decode_video2(states[key].video_codec_ctx, states[key].original_video_frame, &got_picture, states[key].packet); if (decodeValue > 0 && got_picture) { //图片格式转换(上面图片转换准备的参数,在这里使用) sws_scale(states[key].video_convert_ctx, (const uint8_t* const*)states[key].original_video_frame->data, states[key].original_video_frame->linesize, 0, states[key].video_codec_ctx->height, states[key].video_out_frame->data, states[key].video_out_frame->linesize); return 2; } } } } //获取视频缓存大小 //@param key 当前视频的索引值,支持10个视频同时读取 int get_video_buffer_size(int key) { return states[key].video_out_buffer_size; } //获取音频缓存大小 //@param key 当前视频的索引值,支持10个视频同时读取 int get_audio_buffer_size(int key) { return states[key].audio_out_buffer_size; } void set_audio_disabled(int key,bool disabled) { states[key].audioDisabled = disabled; } //获取视频长度 //@param key 当前视频的索引值,支持10个视频同时读取 int get_video_length(int key) { if (states[key].fmt_ctx == NULL) { return -1; } return (int)(states[key].fmt_ctx->duration / 1000000); } bool seek_video(int key,int time) { if (!states[key].isUsed) { return false; } int64_t seek_target = (int64_t)(time * 1.0 / get_video_length(key)*(states[key].fmt_ctx->duration)); if (states[key].fmt_ctx->start_time!= AV_NOPTS_VALUE) { seek_target += states[key].fmt_ctx->start_time; } int64_t seek_min = INT64_MIN; int64_t seek_max = INT64_MAX; int re = avformat_seek_file(states[key].fmt_ctx, -1, seek_min, seek_target, seek_max, 0); states[key].isSeeking = true; thread seekThread(read_frame_thread, key,time); seekThread.detach(); return re >= 0; } //获取当前插件版本 int get_version() { return 2020062201; } // 获取视频帧速率 //@param key 当前视频的索引值,支持10个视频同时读取 double get_video_frameRate(int key) { if (states[key].fmt_ctx == NULL || states[key].video_index == -1) { return 0; } if (states[key].fmt_ctx->streams[states[key].video_index]->r_frame_rate.num > 0 && states[key].fmt_ctx->streams[states[key].video_index]->r_frame_rate.den > 0) { return (av_q2d(states[key].fmt_ctx->streams[states[key].video_index]->r_frame_rate)); } return -1; } //获取音频帧数据 //@param key 当前视频的索引值,支持10个视频同时读取 char *get_audio_frame(int key) { lock_guard<mutex> lock_guard(states[key].lockObj); return (char *)(states[key].audio_out_buffer); } //获取视频帧数据 //@param key 当前视频的索引值,支持10个视频同时读取 char *get_video_frame(int key) { lock_guard<mutex> lock_guard(states[key].lockObj); return (char *)(states[key].video_out_buffer); } //获取视频宽度 //@param key 当前视频的索引值,支持10个视频同时读取 int get_video_width(int key) { return states[key].video_codec_ctx->width; } //获取视频高度 //@param key 当前视频的索引值,支持10个视频同时读取 int get_video_height(int key) { return states[key].video_codec_ctx->height; } // 获取音频采样率 //@param key 当前视频的索引值,支持10个视频同时读取 int get_audio_sample_rate(int key) { return states[key].sample_rate; } // 获取音频声道 //@param key 当前视频的索引值,支持10个视频同时读取 int get_audio_channel(int key) { return states[key].nb_channels; } // 获取当前播放时间 //@param key 当前视频的索引值,支持10个视频同时读取 double get_current_time(int key) { return states[key].playTime; } // 获取当前音频的时间点 //@param key 当前视频的索引值,支持10个视频同时读取 double get_audio_time(int key) { return states[key].audioTime; } //释放资源 //@param key 当前视频的索引值,支持10个视频同时读取 void release(int key) { lock_guard<mutex> lock_guard(states[key].lockObj); states[key].Release(); }