(三) 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
,裡面記錄了FFmpeg
的API
改動。 參考FFmpeg API 變更記錄
SDL
播放音訊是通過回撥函式的方式播放,且這個回撥函式是在新的執行緒中執行,此回撥函式固定時間激發一次,這個時間和要播放的音訊頻率有關係。(和Qt的定時器有點像)
因此,用FFmpeg讀到一幀音訊後,不是著急解碼,而是將資料存入一個佇列,等SDL回撥函式激發的時候,從這個佇列中取出資料,然後解碼播放。
SDL的播放音訊
播放音訊流程
- 初始化
1) 初始化SDL
2) 根據引數(SDL_AudioSpec)開啟音訊裝置 - 迴圈播放資料
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_nofree
的AVPacket
新建一個緩衝區,然後將原緩衝區的資料拷貝至新緩衝區,設定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——基於FFmpeg的Qt音視訊播放器
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
基於Qt、FFMpeg的音視訊播放器設計二(封裝)
在上一篇中我們實現了視訊的解碼、格式轉換,但其基本是堆出來的程式碼,可複用性以及擴充套件性比較低,現在我們對它進行類的封裝。這裡我們把它分為四個小部分。 1、重構封裝FFMpeg類完成開啟和關閉視訊介面 2、重構讀取視訊幀介面 3、重構解碼介面 4、重構ToRGB介面
基於Qt、FFMpeg的音視訊播放器設計四(視訊播放進度控制)
上面介紹瞭如何使用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()獲取系統時鐘