Kaynağa Gözat

VideoDecord 新增使用AVIOContext自定义数据

huihui 4 yıl önce
ebeveyn
işleme
5784e25fad

+ 3 - 0
.gitignore

@@ -9,3 +9,6 @@
 source/OtherDemo/mydemo/FilterDemo/TestAvFilter.pro.user
 /source/build-FFMPEG_Qt-Desktop_Qt_5_13_2_MSVC2017_32bit-Debug
 /source/FFMPEG_Qt/FFMPEG_Qt.pro.user
+source/build-VideoDecode-Desktop_Qt_5_13_2_MSVC2017_32bit_654258-Release
+/build-FFmpegDemo-Desktop_Qt_5_13_2_MSVC2017_32bit_654258-Debug
+/build-FFmpegDemo-Desktop_Qt_5_13_2_MSVC2017_32bit_654258-Release

+ 8 - 0
source/FFmpegDemo.pro

@@ -0,0 +1,8 @@
+TEMPLATE=subdirs
+
+SUBDIRS += FFMPEG_Qt/FFMPEG_Qt.pro
+SUBDIRS += OtherDemo/mydemo/mydemo.pro
+SUBDIRS += AudioEncode/AudioEncode.pro
+SUBDIRS += AudioDecode/AudioDecode.pro
+SUBDIRS += VideoEncode/VideoEncode.pro
+SUBDIRS += VideoDecode/VideoDecode.pro

+ 3 - 0
source/OtherDemo/mydemo/mydemo.pro

@@ -0,0 +1,3 @@
+TEMPLATE=subdirs
+
+SUBDIRS += FilterDemo/TestAvFilter.pro

+ 1 - 1
source/VideoDecode/VideoDecode.pro

@@ -8,7 +8,7 @@ QT       += core gui
 
 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
 
-TARGET = VideoDecode
+TARGET = VideoDecode2
 TEMPLATE = app
 
 CONFIG += c++11

+ 2 - 0
source/VideoDecode/VideoDecoder/VideoDecoder.pri

@@ -7,6 +7,7 @@ SOURCES += \
     $$PWD/src/Mutex/Cond.cpp \
     $$PWD/src/Mutex/Mutex.cpp \
     $$PWD/src/VideoDecoder/VideoDecoder.cpp \
+    $$PWD/src/VideoReader/FFmpegVideoParsing.cpp \
     $$PWD/src/VideoReader/NALUParsing.cpp \
     $$PWD/src/VideoReader/ReadVideoFileThread.cpp
 
@@ -16,6 +17,7 @@ HEADERS += \
     $$PWD/src/Video/VideoEventHandle.h \
     $$PWD/src/Video/VideoFrame.h \
     $$PWD/src/VideoDecoder/VideoDecoder.h \
+    $$PWD/src/VideoReader/FFmpegVideoParsing.h \
     $$PWD/src/VideoReader/h264.h \
     $$PWD/src/VideoReader/h265.h \
     $$PWD/src/VideoReader/NALUParsing.h \

+ 15 - 8
source/VideoDecode/VideoDecoder/src/Mutex/Cond.cpp

@@ -1,10 +1,13 @@
-#include "Cond.h"
+锘�#include "Cond.h"
 
 Cond::Cond()
 {
 #if defined(WIN32) && !defined(MINGW)
     InitializeCriticalSection(&m_mutex);
+
+    ///vista浠ヤ笂鐨勭郴缁熸墠鑳芥墽琛岃繖涓�嚱鏁帮細InitializeConditionVariable
     InitializeConditionVariable(&m_cond);
+
 #else
     pthread_mutex_init(&m_mutex, NULL);
     pthread_cond_init(&m_cond, NULL);
@@ -23,7 +26,7 @@ Cond::~Cond()
 
 }
 
-//加锁
+//鍔犻攣
 int Cond::Lock()
 {
 #if defined(WIN32) && !defined(MINGW)
@@ -35,7 +38,7 @@ int Cond::Lock()
 
 }
 
-//解锁
+//瑙i攣
 int Cond::Unlock()
 {
 #if defined(WIN32) && !defined(MINGW)
@@ -46,10 +49,14 @@ int Cond::Unlock()
 #endif
 }
 
-int Cond::Wait()
+int Cond::Wait(int timeOut)
 {
 #if defined(WIN32) && !defined(MINGW)
-    DWORD ret = SleepConditionVariableCS((PCONDITION_VARIABLE)&m_cond, &m_mutex, INFINITE);
+    if (timeOut < 0)
+    {
+        timeOut = INFINITE;
+    }
+    DWORD ret = SleepConditionVariableCS((PCONDITION_VARIABLE)&m_cond, &m_mutex, timeOut);
 #else
     int ret = pthread_cond_wait(&m_cond, &m_mutex);
 #endif
@@ -58,7 +65,7 @@ int Cond::Wait()
 
 }
 
-//固定时间等待
+//鍥哄畾鏃堕棿绛夊緟
 int Cond::TimedWait(int second)
 {
 #if defined(WIN32) && !defined(MINGW)
@@ -66,7 +73,7 @@ int Cond::TimedWait(int second)
     return 0;
 #else
     struct timespec abstime;
-    //获取从当前时间,并加上等待时间, 设置进程的超时睡眠时间
+    //鑾峰彇浠庡綋鍓嶆椂闂达紝骞跺姞涓婄瓑寰呮椂闂达紝 璁剧疆杩涚▼鐨勮秴鏃剁潯鐪犳椂闂�
     clock_gettime(CLOCK_REALTIME, &abstime);
     abstime.tv_sec += second;
     return pthread_cond_timedwait(&m_cond, &m_mutex, &abstime);
@@ -85,7 +92,7 @@ int Cond::Signal()
     return ret;
 }
 
-//唤醒所有睡眠线程
+//鍞ら啋鎵€鏈夌潯鐪犵嚎绋�
 int Cond::Broadcast()
 {
 #if defined(WIN32) && !defined(MINGW)

+ 9 - 9
source/VideoDecode/VideoDecoder/src/Mutex/Cond.h

@@ -1,8 +1,8 @@
-#ifndef COND_H
+锘�#ifndef COND_H
 #define COND_H
 
-/// 注意Mingw的话使用的是linux下的api pthread
-/// 没有_MSC_VER这个宏 我们就认为他用的是mingw编译器
+/// 娉ㄦ剰Mingw鐨勮瘽浣跨敤鐨勬槸linux涓嬬殑api pthread
+/// 娌℃湁_MSC_VER杩欎釜瀹� 鎴戜滑灏辫�涓轰粬鐢ㄧ殑鏄痬ingw缂栬瘧鍣�
 
 #ifndef _MSC_VER
 #define MINGW
@@ -22,22 +22,22 @@ public:
     Cond();
     ~Cond();
 
-    //上锁
+    //涓婇攣
     int Lock();
 
-    //解锁
+    //瑙i攣
     int Unlock();
 
-    //
-    int Wait();
+    //timeOut-姣��
+    int Wait(int timeOut = -1);
 
-    //固定时间等待
+    //鍥哄畾鏃堕棿绛夊緟
     int TimedWait(int second);
 
     //
     int Signal();
 
-    //唤醒所有睡眠线程
+    //鍞ら啋鎵€鏈夌潯鐪犵嚎绋�
     int Broadcast();
 
 private:

+ 1 - 1
source/VideoDecode/VideoDecoder/src/Mutex/Mutex.cpp

@@ -1,4 +1,4 @@
-#include "Mutex.h"
+#include "Mutex.h"
 
 Mutex::Mutex()
 {

+ 3 - 3
source/VideoDecode/VideoDecoder/src/Mutex/Mutex.h

@@ -1,4 +1,4 @@
-#ifndef MUTEX_H
+锘�#ifndef MUTEX_H
 #define MUTEX_H
 
 
@@ -16,10 +16,10 @@ public:
     Mutex();
     ~Mutex();
 
-    //确保拥有互斥对象的线程对被保护资源的独自访问
+    //纭�繚鎷ユ湁浜掓枼瀵硅薄鐨勭嚎绋嬪�琚�繚鎶よ祫婧愮殑鐙�嚜璁块棶
     int Lock() const;
 
-    //释放当前线程拥有的互斥对象,以使其它线程可以拥有互斥对象,对被保护资源进行访问
+    //閲婃斁褰撳墠绾跨▼鎷ユ湁鐨勪簰鏂ュ�璞★紝浠ヤ娇鍏跺畠绾跨▼鍙�互鎷ユ湁浜掓枼瀵硅薄锛屽�琚�繚鎶よ祫婧愯繘琛岃�闂�
     int Unlock() const;
 
 private:

+ 2 - 2
source/VideoDecode/VideoDecoder/src/VideoDecoder/VideoDecoder.cpp

@@ -1,4 +1,4 @@
-/**
+/**
  * 叶海辉
  * QQ群121376426
  * http://blog.yundiantech.com/
@@ -338,7 +338,7 @@ bool VideoDecoder::decode(uint8_t *inputbuf, int frame_size, uint8_t *&outBuf, i
     av_init_packet(&pkt);
     pkt.data = inputbuf;
     pkt.size = frame_size;
-
+//fprintf(stderr, "%s %d %d \n", __FUNCTION__, pCodecCtx, frame_size);
     if (avcodec_send_packet(pCodecCtx, &pkt) != 0)
     {
        fprintf(stderr, "input AVPacket to decoder failed!\n");

+ 239 - 0
source/VideoDecode/VideoDecoder/src/VideoReader/FFmpegVideoParsing.cpp

@@ -0,0 +1,239 @@
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#include <thread>
+
+#include "FFmpegVideoParsing.h"
+
+#if defined(WIN32)
+#include <WinSock2.h>
+#include <Windows.h>
+#include <direct.h>
+#include <io.h> //C (Windows)    access
+#else
+#include <sys/time.h>
+#include <stdio.h>
+#include <unistd.h>
+
+void Sleep(long mSeconds)
+{
+    usleep(mSeconds * 1000);
+}
+
+#endif
+
+#include <QDebug>
+#include "AppConfig.h"
+
+int read_buffer(void *opaque, uint8_t *buf, int buf_size)
+{
+    FFmpegVideoParsing *pointer = (FFmpegVideoParsing*)opaque;
+
+    int readsize = pointer->readVideoBuffer(buf, buf_size);
+//fprintf(stderr, "%s %d readsize=%d \n", __FUNCTION__, buf_size, readsize);
+    if(readsize > 0)
+    {
+        return readsize;
+    }
+    else
+    {
+        return AVERROR_EOF;
+    }
+}
+
+int MAX_FRAME_SIZE = 1024 * 1024 * 10;
+
+FFmpegVideoParsing::FFmpegVideoParsing()
+{
+    mCallBackFunc = nullptr;
+    mCallBackFuncParam = nullptr;
+
+    mCond = new Cond();
+    mVideoBuffer = (uint8_t*)malloc(MAX_FRAME_SIZE);
+    mVideoBufferSize = 0;
+}
+
+FFmpegVideoParsing::~FFmpegVideoParsing()
+{
+
+}
+
+void FFmpegVideoParsing::start(std::function<void (uint8_t *buffer, const int &size, void *param)> callBackFunc, void *callBackFuncParam)
+{
+    mIsStop = false;
+    mCallBackFunc = callBackFunc;
+    mCallBackFuncParam = callBackFuncParam;
+
+    mVideoBufferSize = 0;
+
+    //启动新的线程实现读取视频文件
+    std::thread([=]
+    {
+        this->run();
+
+    }).detach();
+}
+
+void FFmpegVideoParsing::stop()
+{
+    mIsStop = true;
+
+    while (mIsThreadRunning)
+    {
+        AppConfig::mSleep(100);
+    }
+}
+
+int FFmpegVideoParsing::inputVideoBuffer(const uint8_t * buffer, const int &size)
+{
+    mCond->Lock();
+
+    while ((mVideoBufferSize + size) > MAX_FRAME_SIZE)
+    {
+        mCond->Wait(100);
+    }
+
+    memcpy(mVideoBuffer + mVideoBufferSize, buffer, size);
+    mVideoBufferSize += size;
+    int totalSize = mVideoBufferSize;
+    mCond->Unlock();
+
+    return totalSize;
+}
+
+int FFmpegVideoParsing::readVideoBuffer(uint8_t * bufferOut, const int &size)
+{
+    mCond->Lock();
+
+    while (mVideoBufferSize <= 0)
+    {
+        if (mIsStop)
+        {
+            break;
+        }
+
+        mCond->Wait(100);
+    }
+
+    int bufSize = 0;
+
+    if (mVideoBufferSize > 0)
+    {
+        bufSize = mVideoBufferSize > size ? size : mVideoBufferSize;
+        memcpy(bufferOut, mVideoBuffer, bufSize);
+
+        /// 把后面的数据覆盖上来
+        int leftSize = mVideoBufferSize - bufSize;
+        memmove(mVideoBuffer, mVideoBuffer + bufSize, leftSize);
+        mVideoBufferSize = leftSize;
+    }
+
+    mCond->Unlock();
+
+    return bufSize;
+}
+
+void FFmpegVideoParsing::run()
+{
+    mIsThreadRunning = true;
+
+    #define IO_CTX_BUFFER_SIZE 4096 * 4
+
+    unsigned char *aviobuffer=(unsigned char *)av_malloc(IO_CTX_BUFFER_SIZE);//申请自定义IO的缓冲区,必备,4k对齐
+    AVIOContext *pIOCtx  = avio_alloc_context(aviobuffer, IO_CTX_BUFFER_SIZE, 0, this, read_buffer, NULL, NULL);//opaque为NULL即可
+
+    AVFormatContext *pFormatCtx = nullptr;
+
+do
+{
+
+    AVInputFormat *piFmt = NULL;
+
+    //step2:探测流格式
+    int ret = av_probe_input_buffer(pIOCtx, &piFmt, NULL, NULL, 0, 0);
+
+    if (ret < 0)
+    {
+        fprintf(stderr, "probe failed!\n");
+        break;
+    }
+    else
+    {
+        fprintf(stdout, "probe success!\n");
+        fprintf(stdout, "format: %s[%s]\n", piFmt->name, piFmt->long_name);
+    }
+
+    pFormatCtx = avformat_alloc_context();//建立fmt_ctx格式上下文(统领全文的结构体)
+
+    // Set the IOContext:
+    pFormatCtx->pb = pIOCtx; //关键赋值 ,将自定义的IO赋予fmt_ctx接口
+    pFormatCtx->flags = AVFMT_FLAG_CUSTOM_IO;
+
+
+    //step4:打开流
+    if (avformat_open_input(&pFormatCtx, "", piFmt, NULL) < 0)
+    {
+        fprintf(stderr, "avformat open failed.\n");
+        break;
+    }
+    else
+    {
+        fprintf(stdout, "open stream success!\n");
+    }
+
+    if (avformat_find_stream_info(pFormatCtx, NULL)<0)
+    {
+        fprintf(stdout, "av_find_stream_info error \n");
+        break;
+    }
+
+//    if (ret != 0)
+//    {
+//       char buffer[1024] = {0};
+//       av_strerror(ret, buffer, 1024);
+//       fprintf(stderr, "avformat_open_input ret=%d %s \n", ret, buffer);
+//    }
+
+    fprintf(stderr, "... %d \n", ret);
+
+//    while (1)
+//    {
+//        AVPacket packet;
+//        int ret = av_read_frame(pFormatCtx, &packet);
+
+//        if (ret != 0 && mIsStop)
+//        {
+//            break;
+//        }
+
+//        if (mCallBackFunc != nullptr)
+//        {
+//            mCallBackFunc((uint8_t*)packet.data, packet.size, mCallBackFuncParam);
+//        }
+
+//        av_packet_unref(&packet);
+//    }
+
+
+
+}while(0);
+
+    av_free(pIOCtx->buffer);
+
+    avio_context_free(&pIOCtx);
+
+    if (pFormatCtx != nullptr)
+    {
+        avformat_close_input(&pFormatCtx);
+        avformat_free_context(pFormatCtx);
+    }
+
+    mIsThreadRunning = false;
+
+    fprintf(stdout, " read finished!");
+
+}
+

+ 54 - 0
source/VideoDecode/VideoDecoder/src/VideoReader/FFmpegVideoParsing.h

@@ -0,0 +1,54 @@
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#ifndef FFMPEGVIDEOPARSING_H
+#define FFMPEGVIDEOPARSING_H
+
+#include <thread>
+#include <functional>
+
+extern "C"
+{
+    #include <libavcodec/avcodec.h>
+    #include <libavdevice/avdevice.h>
+    #include <libavformat/avformat.h>
+    #include <libavformat/avformat.h>
+    #include <libswscale/swscale.h>
+    #include <libavutil/imgutils.h>
+}
+
+#include "Mutex/Cond.h"
+
+class FFmpegVideoParsing
+{
+public:
+    FFmpegVideoParsing();
+    ~FFmpegVideoParsing();
+
+    void start(std::function<void (uint8_t *buffer, const int &size, void *param)> callBackFunc, void *callBackFuncParam);
+    void stop();
+
+    int inputVideoBuffer(const uint8_t *buffer, const int &size);
+    int readVideoBuffer(uint8_t *bufferOut, const int &size);
+
+protected:
+    void run();
+
+private:
+    bool mIsStop = false;
+    bool mIsThreadRunning = false;
+
+    Cond *mCond;
+    uint8_t * mVideoBuffer;
+    int mVideoBufferSize;
+
+    ///回调函数,回传读到的视频数据
+    std::function<void (uint8_t *buffer, const int &size, void *param)> mCallBackFunc;
+    void *mCallBackFuncParam;
+
+};
+
+#endif // FFMPEGVIDEOPARSING_H

+ 62 - 14
source/VideoDecode/VideoDecoder/src/VideoReader/ReadVideoFileThread.cpp

@@ -1,4 +1,4 @@
-/**
+/**
  * 叶海辉
  * QQ群121376426
  * http://blog.yundiantech.com/
@@ -31,7 +31,12 @@ ReadVideoFileThread::ReadVideoFileThread()
 {
     mVideoCallBack = nullptr;
 
+#ifdef USE_FFMPEG_VIDEO_PARSE
+    mFFmpegVideoParsing = new FFmpegVideoParsing();
+#else
     mNaluParsing  = new NALUParsing();
+#endif
+
     mVideoDecoder = new VideoDecoder();
 }
 
@@ -45,25 +50,18 @@ void ReadVideoFileThread::startRead(char* filePath, AVCodecID id)
     strcpy(mFileName, filePath);
 
     //启动新的线程实现读取视频文件
-    std::thread([&](ReadVideoFileThread *pointer, AVCodecID id)
+    std::thread([=]
     {
-        pointer->run(id);
+        for (int i=0;i<10000;i++) //测试稳定性
 
-    }, this, id).detach();
+        this->run(id);
+
+    }).detach();
 }
 
 void ReadVideoFileThread::run(AVCodecID id)
 {
-    mVideoDecoder->openDecoder(id);
-
-    if (id == AV_CODEC_ID_H264)
-    {
-        mNaluParsing->setVideoType(T_NALU_H264);
-    }
-    else
-    {
-        mNaluParsing->setVideoType(T_NALU_H265);
-    }
+    bool openDecorderSucceed = mVideoDecoder->openDecoder(id);
 
     char *fileName = mFileName;
     FILE *fp = fopen(fileName, "rb");
@@ -75,6 +73,55 @@ void ReadVideoFileThread::run(AVCodecID id)
 
     int frameNum = 0; //当前播放的帧序号
 
+#ifdef USE_FFMPEG_VIDEO_PARSE
+
+    auto getVideoFrameFunc = [&](uint8_t *buffer, const int &size, void *param)
+    {
+        uint8_t *bufferYUV = nullptr;
+        int width  = 0;
+        int height = 0;
+fprintf(stderr, "%s %d %d %d\n", __FUNCTION__, buffer, size, openDecorderSucceed);
+        mVideoDecoder->decode(buffer, size, bufferYUV, width, height);
+
+        if (bufferYUV != nullptr)
+        {
+            int frameRate = mVideoDecoder->getFrameRate(); //获取帧率
+
+            /// h264裸数据不包含时间戳信息  因此只能根据帧率做同步
+            /// 需要成功解码一帧后 才能获取到帧率
+            /// 为0说明还没获取到 则直接显示
+            if (frameRate != 0)
+            {
+                Sleep(1000 / frameRate);
+            }
+
+            //然后传给主线程显示
+            doDisplayVideo(bufferYUV, width, height, ++frameNum);
+        }
+    };
+
+    mFFmpegVideoParsing->start(getVideoFrameFunc, nullptr);
+
+    while(!feof(fp))
+    {
+        char buf[10240] = {0};
+        int size = fread(buf, 1, 1024, fp);//从h264文件读1024个字节 (模拟从网络收到h264流)
+        int nCount = mFFmpegVideoParsing->inputVideoBuffer((const uint8_t*)buf, size);
+    }
+
+    fclose(fp);
+
+    mFFmpegVideoParsing->stop();
+
+#else
+    if (id == AV_CODEC_ID_H264)
+    {
+        mNaluParsing->setVideoType(T_NALU_H264);
+    }
+    else
+    {
+        mNaluParsing->setVideoType(T_NALU_H265);
+    }
     while(!feof(fp))
     {
         char buf[10240];
@@ -119,6 +166,7 @@ void ReadVideoFileThread::run(AVCodecID id)
             NALUParsing::FreeNALU(nalu);
         }
     }
+#endif
 
     mVideoDecoder->closeDecoder();
 }

+ 9 - 1
source/VideoDecode/VideoDecoder/src/VideoReader/ReadVideoFileThread.h

@@ -1,4 +1,4 @@
-/**
+/**
  * 叶海辉
  * QQ群121376426
  * http://blog.yundiantech.com/
@@ -9,9 +9,12 @@
 
 #include "VideoDecoder/VideoDecoder.h"
 #include "VideoReader/NALUParsing.h"
+#include "VideoReader/FFmpegVideoParsing.h"
 
 #include "Video/VideoEventHandle.h"
 
+#define USE_FFMPEG_VIDEO_PARSE
+
 class ReadVideoFileThread
 {
 public:
@@ -30,7 +33,12 @@ protected:
     void run(AVCodecID id);
 
 private:
+
+#ifdef USE_FFMPEG_VIDEO_PARSE
+    FFmpegVideoParsing *mFFmpegVideoParsing;
+#else
     NALUParsing *mNaluParsing;
+#endif
     VideoDecoder *mVideoDecoder;
 
     char mFileName[256];