1. 程式人生 > >(三) FFmpeg結合Qt實現視訊播放器(一)

(三) FFmpeg結合Qt實現視訊播放器(一)

目錄

前言

(二) FFmpeg解碼視訊學習已經完成了Qt實現視訊播放器的一部分,但是沒有音訊,而且在播放大的視訊的時候,甚至會到導致我的Ubuntu卡死。繼續跟著大神們學習!

SDL的使用

SDL概述

主流的音訊開源庫:OpenAL、PortAudio、SDL、SDL_audioin。其中SDL資料多,學習方便;跨平臺;庫體積相對比較小。但是SDL**不能採集音訊**,FFmpeg支援音訊採集,必要的時候可以直接使用FFmpeg採集。SDL本身是一個多媒體庫,其最強大的地方不是在音訊上,而是在圖形顯示上。這裡只是使用SDL來播放音訊。

SDL下載地址

SDL的linux下編譯

配置選項:

./configure CC=arm-linux-gnueabihf-gcc --host=arm-linux --prefix=/grapeRain/grape_SDL2 --disable-static --enable-shared --disable-pulseaudio --disable-esd
make -j4
make install

注:--disable-pulseaudio --disable-esd是因為編譯之後出現錯誤,所以我disable了這兩個選項,之後編譯就通過了,所以我也不知道這兩個編譯選項的含義。

SDL的使用

參考從零開始學習音視訊程式設計技術(七) FFMPEG Qt視訊播放器之SDL的使用

中提供的專案原始碼。
注意:在FFmpeg3.4.1這個版本中,avcodec_alloc_frame()函式已經廢棄了,使用av_frame_alloc()替換。同時,avcodec_get_frame_defaults()也已經廢棄,具體可以檢視FFmpeg的文件。文件路徑為:/xxx/ffmpeg-3.4.1/doc/下的APIchanges,裡面記錄了FFmpegAPI改動。
參考FFmpeg API 變更記錄

SDL播放音訊是通過回撥函式的方式播放,且這個回撥函式是在新的執行緒中執行,此回撥函式固定時間激發一次,這個時間和要播放的音訊頻率有關係。(和Qt的定時器有點像)
因此,用FFmpeg讀到一幀音訊後,不是著急解碼,而是將資料存入一個佇列,等SDL回撥函式激發的時候,從這個佇列中取出資料,然後解碼播放。

SDL的播放音訊

播放音訊流程

  1. 初始化
    1) 初始化SDL
    2) 根據引數(SDL_AudioSpec)開啟音訊裝置
  2. 迴圈播放資料
    1) 播放音訊資料
    2) 延時等待播放完成

SDL結構體

  • SDL_AudioSpec
typedef struct SDL_AudioSpec  
{  
    int freq;                   音訊資料取樣率 常用48000 44100
    SDL_AudioFormat format;     音訊資料的格式。
    Uint8 channels;             聲道數。例如單聲道取值為1,立體聲取值為2
    Uint8 silence;              設定靜音的值
    Uint16 samples;             音訊緩衝區中的取樣個數,要求必須是2的n次方
    Uint16 padding;             考慮到相容性的一個引數
    Uint32 size;                音訊緩衝區的大小,以位元組為單位
    SDL_AudioCallback callback; 填充音訊緩衝區的回撥函式
    void *userdata;             使用者自定義的資料
} SDL_AudioSpec; 

注:SDL_AudioFormat format;—-常用的音訊資料格式:

AUDIO_U16SYS:unsigned 16-bit samples
AUDIO_S16SYS:Signed 16-bit samples
AUDIO_S32SYS:32-bit integer samples
AUDIO_F32SYS:32-bit floating point samples

SDL的API

  • SDL_Init()
// 功能:初始化SDL
int SDLCALL SDL_Init(Uint32 flags);
// 引數:flags
SDL_INIT_TIMER:定時器
SDL_INIT_AUDIO:音訊
SDL_INIT_VIDEO:視訊
SDL_INIT_JOYSTICK:搖桿
SDL_INIT_GAMECONTROLLER:遊戲控制器
SDL_INIT_NOPARACHUTE:不捕獲關鍵訊號
SDL_INIT_EVERYTHING:包含上述所有選項
  • SDL_OpenAudio()
    SDL_OpenAudio開啟SDL播放裝置
  • SDL_PauseAudio()
// 功能:當pause_on設定為0即開始播放音訊資料。設定為1的時候,將會播放靜音的值
void SDLCALL SDL_PauseAudio(int pause_on)
  • 回撥函式
    當音訊裝置需要更多資料時,會呼叫這個回撥函式。回撥函式格式如:
void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 * stream,  
                                            int len);  
userdata: SDL_AudioSpec結構中的使用者自定義資料,一般情況可以不用。
stream: 該指標指向需要填充的音訊緩衝區

SDL互斥量和條件變數

在進行音視訊播放的過程中,會把解複用和解碼放在不同的執行緒中,存放包的佇列是公共資源,需要互斥訪問。具體就是解複用向佇列中新增包,解碼從佇列中取包,也就需要同步。所以在佇列的入隊和出對操作中,採用了互斥量和條件變數。

互斥量

  • 建立互斥量
SDL_mutex *mutex;
mutex = SDL_CreateMutex();

建立的互斥量預設是未上鎖的。
- 上鎖和解鎖

SDL_LockMutex(mutex);
SDL_UnlockMutex(mutex);
  • 銷燬鎖
SDL_DestroyMutex(mutex);

條件變數

  • 建立條件變數
SDL_cond* SDL_CreateCond(void) 
  • 等待條件變數
int SDL_CondWait(SDL_cond*  cond,  
                 SDL_mutex* mutex) 

訊號啟用後,返回0,否則返回錯誤程式碼。
SDL_CondWait必須在互斥量鎖住之後才能呼叫。該函式會解鎖鎖住的互斥量,並等待擁有該鎖的執行緒啟用訊號。啟用後,重新上鎖。
- 啟用訊號

int SDL_CondSignal(SDL_cond* cond)  

SDL_CondSignal會啟用等待的一個執行緒(根據優先順序),而不是所有等待的執行緒。

程式碼

typedef struct PacketQueue
{
    AVPacketList *first_pkt, *last_pkt;
    int nb_packets;
    int size;
    SDL_mutex *mutex;
    SDL_cond *cond;
}PacketQueue_t;
void packet_queue_init(PacketQueue *q)
{
    memset(q, 0, sizeof(PacketQueue));
    q->mutex = SDL_CreateMutex();
    q->cond  = SDL_CreateCond();
    q->size  = 0;
    q->nb_packets = 0;
    q->first_pkt  = NULL;
    q->last_pkt   = NULL;
}

int packet_queue_put(PacketQueue *q, AVPacket *pkt)
{
    AVPacketList *pkt1;
    if (av_dup_packet(pkt) < 0) {
        return -1;
    }

    pkt1 = (AVPacketList*)av_malloc(sizeof(AVPacketList));
    if (!pkt1)
    {
        return -1;
    }
    pkt1->pkt = *pkt;
    pkt1->next = NULL;

    SDL_LockMutex(q->mutex);
    if(!q->last_pkt)
    {
        q->first_pkt = pkt1;
    }
    else
    {
        q->last_pkt->next = pkt1;
    }

    q->last_pkt = pkt1;             // pointer to the add one packet
    q->nb_packets++;
    q->size += pkt1->pkt.size;

    SDL_CondSignal(q->cond);
    SDL_UnlockMutex(q->mutex);

    return 0;
}

int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
    AVPacketList *pkt1;
    int ret;

    SDL_LockMutex(q->mutex);

    for(;;)
    {
        pkt1 = q->first_pkt;
        if(pkt1)
        {
            q->first_pkt = pkt1->next;
            if(!q->first_pkt)
            {
                q->last_pkt = NULL;
            }
            q->nb_packets--;
            q->size -= pkt1->pkt.size;
            av_free(pkt);
            ret = 1;
            break;
        }
        else if(!block)
        {
            ret = 0;
            break;
        }
        else
        {
            SDL_CondWait(q->cond, q->mutex);
        }
    }

    SDL_UnlockMutex(q->mutex);

    return ret;
}

理解來說,假設packet_queue_put是一個生產者,一直往佇列裡填資料;而packet_queue_get是消費者,一直從佇列中取資料。當packet_queue_get從佇列中取資料時,發現佇列中已經沒有資料了,就會呼叫SDL_CondWait進入等待狀態(阻塞),同時釋放互斥鎖。這時候,packet_queue_put往佇列中填入資料後,呼叫SDL_CondSignal,喚醒packet_queue_get讀取資料,並重新上鎖。在讀取資料完畢之後,釋放鎖。
博主記:以上是博主的個人理解,可能有不對的地方,見諒!!

FFmpeg函式解析

結構體

  • AVPacket
    每一個Packet是一個完整的幀,用來暫存解複用之後、解碼之前的媒體資料
typedef struct AVPacket  
{  
    int64_t pts;    // 顯示時間戳
    int64_t dts;    // 解碼時間戳
    int64_t pos;  
    uint8_t *data;  // 資料首地址  
    int size;  
    int stream_index; // 所屬媒體流的索引  
    int flags;        // flags為標誌域,1表示該資料是一個關鍵幀  
    void(*destruct)(struct AVPacket*);  // 釋放資料緩衝區的函式指標  
} AVPacket;  

AVPacket本身只是一個容器,其中data成員指向實際的資料緩衝區。這個緩衝區通常由av_new_packet建立,也可能由FFMPEG的API建立。當某個AVPacket結構的資料緩衝區不再被使用時,需要通過呼叫av_free_packet釋放。

FFmpeg內部使用AVPacket建立緩衝區裝載資料,同時提供destruct函式,如果FFmpeg打算自己維護緩衝區,則將destruct設為av_destruct_packet_nofree,使用者呼叫av_free_packet清理緩衝區時並不能將其釋放(共享緩衝區);如果FFmpeg打算將該緩衝區徹底交給呼叫者,則將destruct設為av_destruct_packet,表示它能夠被釋放。安全起見,如果使用者希望自由使用一個FFmpeg內部建立的AVPacket,最好呼叫av_dup_packet進行緩衝區的克隆,將其轉化為緩衝區能夠被釋放的AVPacket,以免對緩衝區的不當佔用造成異常錯誤。av_dup_packet會為destruct指標為av_destruct_packet_nofreeAVPacket新建一個緩衝區,然後將原緩衝區的資料拷貝至新緩衝區,設定data的值為新緩衝區的地址,同時設定destruct指標為av_destruct_packet
- AVPacketList

typedef struct AVPacketList {  
    AVPacket pkt;       // AVPacket 
    struct AVPacketList *next; // next是一個AVPacketList指標  
} AVPacketList;  
  • PacketQueue
typedef struct PacketQueue {  
  AVPacketList *first_pkt, *last_pkt; // first_pkt指向AVPacketList的頭 last_pkt指向AVPacketList的尾  
  int nb_packets;  // Packet佇列的中AV_Packet的個數  
  int size;  
  SDL_mutex *mutex;  
  SDL_cond *cond;  
} PacketQueue;

函式

  • av_dup_packet(AVPacket* pkt)
int av_dup_packet(AVPacket *pkt)
{
    if (((pkt->destruct == av_destruct_packet_nofree) || (pkt->destruct == NULL)) && pkt->data) {
    uint8_t *data;

    if((unsigned)pkt->size > (unsigned)pkt->size + FF_INPUT_BUFFER_PADDING_SIZE)
    return AVERROR(ENOMEM);
    data = av_malloc(pkt->size + FF_INPUT_BUFFER_PADDING_SIZE);
    if (!data) {
        return AVERROR(ENOMEM);
    }
    memcpy(data, pkt->data, pkt->size);
    memset(data + pkt->size, 0, FF_INPUT_BUFFER_PADDING_SIZE);
    pkt->data = data;
    pkt->destruct = av_destruct_packet;
    }
    return 0;
}

av_destruct_packet_nofree表示AVPacket由FFmpeg維護,換句話來說,就是由FFmpeg來管理釋放。既然由FFmpeg來管理,作用可以用於共享,推測就是資料可以拷貝不可以被隨意修改,如果需要資料被修改,需要使用av_dup_packet拷貝一份資料。

總結

參考連結

相關推薦

() FFmpeg結合Qt實現視訊播放()

目錄 前言 (二) FFmpeg解碼視訊學習已經完成了Qt實現視訊播放器的一部分,但是沒有音訊,而且在播放大的視訊的時候,甚至會到導致我的Ubuntu卡死。繼續跟著大神們學習! SDL的使用 SDL概述 主流的音訊開源庫:OpenA

QtPlayer——基於FFmpegQt視訊播放

QtPlayer——基於FFmpeg的Qt音視訊播放器 本文主要講解一個基於Qt GUI的,使用FFmpeg音視訊庫解碼的音視訊播放器,同時也是記錄一點學習心得,本人也是多媒體初學者,也歡迎大家交流,程式執行圖如下: QtPlayer基於FFmpeg的Q

qt實現視訊播放

本篇部落格介紹如何利用qMediaPlayer和qvideowidget實現視訊檔案(avi,mp4....)的播放,並且提供進度顯示,還可以通過拖動進度條來變換播放位置。相關程式碼可以在我的資源裡下載"基於qt的視訊播放器" pro檔案: #------------------------

android,Exoplayer實現視訊播放

bundle配置: implementation 'com.google.android.exoplayer:exoplayer-core:2.8.1'implementation 'com.google.android.exoplayer:exoplayer-dash:2.8.1'implementati

從零開始仿寫一個抖音App——基於FFmpeg的極簡視訊播放

本文發於掘金——何時夕,搬運轉載請註明出處,否則將追究版權責任。交流qq群:859640274 GitHub地址 好久不見,最近加班比較多所以第二篇音視訊方面的文章 delay 了一週,大家多包涵哈。本文預計閱讀時間二十分鐘。 本文分為以下章節,讀者可以按需閱讀 1.FFmpeg原始碼

最簡單的基於FFMPEG+SDL的音視訊播放

一、概述         在《最簡單的基於FFMPEG+SDL的音訊播放器》記錄一中,我們實現了音訊的播放。更早前,我們在《最簡單的基於FFMPEG+SDL的視訊播放器》記錄一和二中,實現了視訊的播放。在實現視訊播放的時候,我們設定了一個延遲40ms,否則視訊就會以解碼的速

js實現視訊播放

前言 之前使用js實現過音樂播放控制條,最終存在幾點不如人意的地方,具體的幾點如下: 1、存在拖動按鈕不流暢 2、時間處理的問題,時間點在播放過程中存在重複的問題 本篇文章,採用input range以及progress來實現進度,避免在js

Qt編寫視訊播放(vlc核心)

在研究qt+vlc的過程中,就想直接做個播放器用於獨立的專案,vlc還支援硬體加速,不過部分電腦硬體不支援除外。用vlc的核心寫播放器就是快,直接呼叫api就行,邏輯處理和ui展示基本上分分鐘的事情,最好加點美化那就更加完美了,市面上很多播放器是vlc核心寫的,或者ffmpe

安卓實現視訊播放

package com.example.g150825_android27; import android.media.MediaRecorder; import android.os.Bundle; import android.support.annota

html5視訊播放 (改寫預設樣式)

一個專案用到了html5視訊播放器,於是就寫了一個,走了很多坑,例如在chrome中載入視訊出現載入異常等 先看看效果 是不是感覺換不錯,以下是我播放器改寫樣式的佈局。 <!DOCT

android平臺下基於ffmpeg和ANativeWindow實現簡單的視訊播放

音視訊實踐學習 android全平臺編譯ffmpeg以及x264與fdk-aac實踐 ubuntu下使用nginx和nginx-rtmp-module配置直播推流伺服器 android全平臺編譯ffmpeg合併為單個庫實踐 android-studio使用c

基於QtFFMpeg的音視訊播放設計二(封裝)

在上一篇中我們實現了視訊的解碼、格式轉換,但其基本是堆出來的程式碼,可複用性以及擴充套件性比較低,現在我們對它進行類的封裝。這裡我們把它分為四個小部分。 1、重構封裝FFMpeg類完成開啟和關閉視訊介面 2、重構讀取視訊幀介面 3、重構解碼介面 4、重構ToRGB介面

基於QtFFMpeg的音視訊播放設計四(視訊播放進度控制)

上面介紹瞭如何使用opengl繪製視訊和Qt的介面設計,也比較簡單,現在我們看下如何控制視訊播放及進度的控制,內容主要分為以下幾個部分 1、建立解碼執行緒控制播放速度 2、通過Qt開啟外部視訊 3、視訊總時間顯示和播放的當前時間顯示 4、進度條顯示播放進度、拖動進度條

100行程式碼實現最簡單的基於FFMPEG+SDL的視訊播放(SDL1.x)

                =====================================================最簡單的基於FFmpeg的視訊播放器系列文章列表:=====================================================簡介FFMPEG

[6] ffmpeg + SDL2 實現視訊播放「視音訊同步」

日期:2016.10.8 作者:isshe github:github.com/isshe 郵箱:[email protected] 平臺:ubuntu16.04 64bit

基於FFmpeg視訊播放開發系列教程(

   本篇開始講解音訊解碼播放,該專案用Qt的音訊類QAudioFormat, QAudioOutput等進行解碼,先講解一些關於音訊的知識。 1.取樣頻率   指每秒鐘取得聲音樣本的次數。取樣的過程就是抽取某點的頻率值,很顯然,在一秒中內抽取的點越多,獲取得頻率資

從零開始學習音視訊程式設計技術(七) FFMPEG Qt視訊播放之SDL的使用

前面介紹了使用FFMPEG+Qt解碼視訊並顯示。 現在我們就著手給它加上聲音播放。 播放聲音有很多種方式: 以windows系統為例,可以使用如下方法播放音訊: 1.直接呼叫系統API的wavein、waveout等函式 2.使用directsound播放

從零實現簡易播放:4.ffmpeg 解碼視訊為yuv資料-使用avcodec_send_packet與avcodec_receive_frame

ffmpeg 解碼視訊為yuv資料 作者:史正 郵箱:[email protected] 如有錯誤還請及時指正 如果有錯誤的描述給您帶來不便還請見諒 如需交流請傳送郵件,歡迎聯絡 csdn : https://blog.csdn.net/shizheng163 g

XCode版【100行程式碼實現最簡單的基於FFMPEG+SDL的視訊播放

【來自】 1.新建XCode工程後,發現即使安裝了SDL和FFMPEG也編譯不成功,需要修改各種環境。經過我的不懈努力加百穀啥的...貼個能編譯通過的過程出來。謹記! 2.首先需要編譯好ffmpeg原始碼,然後還需要安裝SDL(ffmpeg直接編譯,SDL我是通過brew安

從零開始學習音視訊程式設計技術(八)FFMPEG Qt視訊播放之音視訊同步

前面分別講解了: 現在我們就將視訊和音訊合併,並讓聲音和畫面同步。 加入音訊的部分就不做講解了,這裡主要講下聲音和視訊同步的步驟。 首先剛開始播放的時候通過av_gettime()獲取系統主時鐘,記錄下來。 以後便不斷呼叫av_gettime()獲取系統時鐘