zoukankan      html  css  js  c++  java
  • 视频播放器-使用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("读取视频帧
    ");
        //解码。输入为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
    ", 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();
    }
  • 相关阅读:
    linux之SQL语句简明教程---CONCATENATE
    linux之SQL语句简明教程---UNION ALL
    linux之SQL语句简明教程---UNION
    linux之SQL语句简明教程---Subquery
    linux之SQL语句简明教程---外部连接
    Oracle11g温习-第五章:数据字典
    Oracle11g温习-第四章:手工建库
    Oracle11g温习-第三章:instance实例管理
    Oracle11g温习-第一章 3、ORACLE逻辑结构
    Oracle11g温习-第一章:Oracle 体系架构
  • 原文地址:https://www.cnblogs.com/sauronKing/p/13322439.html
Copyright © 2011-2022 走看看