視頻播放器-使用FFMPEG技術對視頻解封裝和解碼


視頻播放器-視頻播放前期調研

視頻播放器-使用FFMPEG技術對視頻解封裝和解碼

視頻播放器-使用SoundTouch算法庫對聲音進行變速

視頻播放器-使用OpenAL技術播放聲音

視頻播放器-使用封裝的C++插件在Unity3d中播放視頻

視頻播放器-FFMPEG官方庫,包含lib,include,bin x64和x86平台的所有文件,提取碼4v2c

視頻播放器-LQVideo實現視頻解碼C++源代碼,提取碼br9u

視頻播放器-SoundTouch實現聲音變速的C++源代碼,提取碼6htk

視頻播放器-官方openal安裝文件,提取碼yl3j

視頻播放器-OpenAL實現音頻播放功能,提取碼mjp2

 

本文主要是根據項目需求實現視頻解碼的底層封裝,最終導出的是dll文件,供unity3d插件調用,本文主要分為三部分

  1. 環境配置
  2. 根據需求設計接口,實現接口
  3. 需要注意的問題

首先必須聲明,第一部分和第二部分接口的實現我都是站在前人的肩膀上做的。為了功能的完整性,我會進行介紹,但是版權不是我的哦。

參考文章:

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兩個文件夾

image

4. 創建C++工程,我使用的是VS2017,創建類庫程序或者控制台程序都可以,創建完成后,右鍵項目,選擇屬性,需要配置的項如下圖所示

image

  • 第一項選擇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

接口設計及實現

既然環境配置完成了,接下來就需要設計我們的接口以及實現接口,在這一步之前,我需要先給定幾個說明和假設

  1. 我們的接口是給Unity3d插件調用的,目前unity3d調用C++接口有兩種方式,通過DllImport調用(Native調用)和通過引用調用(Managed調用),雖然Managed調用方便跨平台,但是由於unity3d規定,Managed調用需要使用托管C++並且使用的clr運行時必須是安全的,這樣FFMPEG底層的庫就都不能用了,所以我們采用Native調用。既然這樣,C++接口的定義方式必須是如下的形式:
    extern "C" _declspec(dllexport) int init_ffmpeg(char* url);
  2. 強烈建議讀一下開始給出的雷神寫的兩篇博客,至少了解一下視頻解碼的過程,我下面給出的詳細的過程但是沒有說明:
    注冊組件->解封裝視頻獲取上下文->獲取流信息->查找音頻視頻解碼器->打開音頻視頻解碼器->獲取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()
{
}

是不是很簡單Smile

 

接下來就剩接口的實現類了,因為代碼有點多,我把代碼放到最后,通過這篇文章,我所期望達到的目的就是

  1. 按照配置步驟配置環境
  2. 將所有的代碼拷貝到代碼文件中
  3. 編譯,通過
  4. 可以創建控制台項目,在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();
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM