視頻播放器-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(); }