SauronKing

写随笔只是为了记录自己的曾经,如果能给您带来些许方便,那是我莫大的荣幸!

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

视频播放器-视频播放前期调研

视频播放器-使用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();
}
posted on 2020-07-16 14:24  SauronKing  阅读(1174)  评论(0编辑  收藏  举报