ffmpeg提取音頻播放器總結;
一:簡介
從編寫音頻播放器代碼到完成播放器編寫,測試,整整5天的時間,這時間還不算之前對 ffmpeg熟悉的時間,可以說是歷經千辛萬苦,終於搞出來了,雖然最終效果還不是很理想,但是已經可以很流暢的播放某些歌曲了,說是某些歌曲,是因爲還有些歌曲播放效果不是很好,有些許雜音,至於那些歌曲能夠順利播放,那些不能夠,我現在也摸不準是什麼原因導致的,有待進一步鑽研,等啥時候調好了,就用自己的這個播放器聽歌曲了,嘿嘿;
a:插播:)
/**************/
這一部分屬於插播內容,就不用看了;
tv視頻播放;
採用img_convert時,是轉換成24RGB快呢,還是32RGB快呢?可能前者快吧;似乎用qimage的話只能轉換成32RGB了;因爲它只有三種顏色深度1-p, 8-p, 32-p所以,只能選擇32-p了;
下面是AVFrame的結構,具體可以看這裏:
http://cekirdek.pardus.org.tr/~ismail/ffmpeg-docs/avcodec_8h-source.html#l00424
就是一個宏定義,咳。。。
/**************/
二:音頻播放器原理
音頻播放器過程如下所示:
打開文件--分析文件格式--打開對應解碼器--讀取一音頻幀--解碼音頻幀--音頻數據寫入音頻設備--循環讀取音頻幀--再解碼。。。如此循環下去;
整個播放器實現原理詳細說明爲,採用ffmpeg提供的API函數先用av_open_input_file打開音頻文件,分析文件得到格式信息,然後識別格式,並查找對應的解碼器,再得到針對此音頻文件的解碼器之後,用av_read_frame從音頻文件中讀取一幀,然後對其用 avcodec_decode_audio函數進行解碼,在將解碼之後的PCM音頻數據直接寫到audio設備(/dev/dsp)上,根據linux音頻設備的原理,此時你就應該聽到悅耳的歌聲了;
三:重點要點說明
在這個過程當中有幾處需要特別注意,下面詳細說明一下:
1、不同音頻文件格式對音頻壓縮率不同,導致對於同一個音頻包,你解壓出來的音頻數據大小也是不一樣的,這一點無需驚奇,但是對於這些解壓出來的音頻數據,一定要保證全部寫到聲卡當中去,這樣才能夠作爲你能聽到悅耳歌聲的基礎,這裏留意一下,這只是一個基礎,要想完全實現好此播放器,下一點更是不可或缺的;我之前之所以在調試時總是聽到聲音很雜亂,或者帶有金屬聲,就是因爲聲音沒有全部寫到音頻設備中去,當然,可能或多或少也有一些寫音頻數據的太快的原故;
2、在確認瞭解碼後的數據是完整的之後,可以將數據寫入到音頻設備當中了(/dev/dsp),這裏很關鍵的一點就是要對音頻設備進行設置,否則你也聽不到你想聽到的聲音:(
對音頻設備的設置主要是四個方面,這不代表其他方面不設置哦:
設置採樣率(有關音頻採樣率,在我blog前面的文章當中有說明,一般有44100hz,48000hz,22050?不記得了,你查看我blog中前面的文章吧,嘿嘿):
ioctl (fd, SNDCTL_DSP_SPEED, &(pCodecCtx->sample_rate));
設置音頻聲道數(這個很好理解,一般都是立體聲了)
// set channels;
i = pCodecCtx->channels;
#ifdef AUDIO_DEBUG
printf ("pCodecCtx->channels:%d/n", pCodecCtx->channels);
#endif
if ((ioctl (fd, SNDCTL_DSP_CHANNELS, &i)) == -1)
{
fprintf (stderr, "Set Audio Channels %d failed:%s/n", i,
strerror (errno));
return (-1);
}
這裏需要說明的一點是,如果是立體聲,則此處i應該等於2,而不是1,網上很多文章這裏都說明的不正確,我之前就一直以爲立體聲爲1,單聲道爲0,總是不出聲音,後來一狠心,改爲2,盡然ok,faint;
結論:網上的東西啊,不可全信之。。。。。。。。。。。。。。。。。。。。。。。。。。。
設置量化位數(這個量化位數是指對聲音的振幅進行採樣的位數,以前一般是8位,現在以16位居多,更高位數對於普通用戶用不着,只能在專業音樂中才有價值)
i = AFMT_S16_LE; (16位,小端存儲,也即intel的倒序數據存儲)
ioctl (fd, SNDCTL_DSP_SETFMT, &i);
設置音頻驅動級緩存
i = (0x0004 << 16) + 0x000b; // four 2kb buffer;你看着對應改就行了(這裏是四個2kb緩存)
ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &i);
這裏也可以不設置,用系統默認自定義也可;
另外有一個疑問也順帶解決了:
Q:播放音頻和pts有關係麼?需要他來調整播放的快慢麼?就像視頻那樣?
A:基本沒有關係,至少我目前沒有用到這個咚咚,pts應該實在視頻當中採用到的,pts是顯示時間戳,dts是解碼時間戳;以後搞到視頻再詳細說明啦;不需要,對於寫音頻數據,系統,或者更準確的說驅動會自動調整,寫得快,他會阻塞你的寫,寫的慢?你的機器該換了,嘿嘿,玩笑一個。。。增加緩存可以解決慢的問題;
Q:如何調試音頻播放器?
這裏需要注意兩點,一點是你要保證解碼後的數據確實是PCM數據;第二點是你要確定數據準確無誤,全部寫入音頻文件,否則會出現各種各樣的暴音啊之類的事情,不可預測;
有關這兩點你可以分別調試;第一點,可以將解碼後的數據寫入一個文件當中,然後利用一些音頻分析軟件(能夠分析PCM數據),播放即可,看你解碼的數據是否正確,完整,如果沒有問題,那這一步就完成了,我在這裏沒有卡殼,直接過;下一步,我是扔進去很多時間,由於我的指針使用不當,導致總是漏寫數據,我在下面也會把我的錯誤代碼貼出來,以做對比,大家也都可以來看看,這一點我想經常和指針打交道的就肯定沒問題了的;
這裏向大家推薦windows下的cooledit軟件,不用找註冊碼,反正能試用,沒問題,功能絕對夠用,而且分析聲音頻播非常形象,鄭重推薦;雖然windows和linux切換麻煩了點,嘿嘿:)不過如果你有兩臺電腦,另說啦。。。
下面將這個音頻播放器的源代碼貼出來,以便大家互相學習;
我的編譯環境是
os:Neoshine linux (2.6.14-1.1644_dt_5);
硬件:普通pc機;
/***************************************************************************
* main.cc
*
* Thu Nov 9 20:47:33 2006
* Copyright 2006
* Email lsosa.BIT
* Author lsosa.BIT
****************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ALL_DEBUG
#ifdef ALL_DEBUG
#define AV_DEBUG
#define AUDIO_DEBUG
#endif
//------------------------------------------------------------------------------
// manipulations for file
int open_file (char *file_name, int mode)
{
// open file file_name and return the file descriptor;
int fd;
if ((fd = open (file_name, mode)) < 0)
{
fprintf (stderr, " Can't open %s!/n", file_name);
exit (-1);
}
return fd;
}
int set_audio (int fd, AVCodecContext * pCodecCtx)
{
// set the properties of audio device with pCodecCtx;
int i, err;
/* 設置適當的參數,使得聲音設備工作正常 */
/* 詳細情況請參考Linux關於聲卡編程的文檔 */
i = 0;
ioctl (fd, SNDCTL_DSP_RESET, &i);
i = 0;
ioctl (fd, SNDCTL_DSP_SYNC, &i);
i = 1;
ioctl (fd, SNDCTL_DSP_NONBLOCK, &i);
// set sample rate;
#ifdef AUDIO_DEBUG
printf ("pCodecCtx->sample_rate:%d/n", pCodecCtx->sample_rate);
#endif
i = pCodecCtx->sample_rate;
if (ioctl (fd, SNDCTL_DSP_SPEED, &i) == -1)
{
fprintf (stderr, "Set speed to %d failed:%s/n", i,
strerror (errno));
return (-1);
}
if (i != pCodecCtx->sample_rate)
{
fprintf (stderr, "do not support speed %d,supported is %d/n",
pCodecCtx->sample_rate, i);
return (-1);
}
// set channels;
i = pCodecCtx->channels;
#ifdef AUDIO_DEBUG
printf ("pCodecCtx->channels:%d/n", pCodecCtx->channels);
#endif
if ((ioctl (fd, SNDCTL_DSP_CHANNELS, &i)) == -1)
{
fprintf (stderr, "Set Audio Channels %d failed:%s/n", i,
strerror (errno));
return (-1);
}
if (i != pCodecCtx->channels)
{
fprintf (stderr, "do not support channel %d,supported %d/n",
pCodecCtx->channels, i);
return (-1);
}
// set bit format;
i = AFMT_S16_LE;
if (ioctl (fd, SNDCTL_DSP_SETFMT, &i) == -1)
{
fprintf (stderr, "Set fmt to bit %d failed:%s/n", i,
strerror (errno));
return (-1);
}
if (i != AFMT_S16_LE)
{
fprintf (stderr, "do not support bit %d, supported %d/n",
AFMT_S16_LE, i);
return (-1);
}
// set application buffer size;
// i = (0x00032 << 16) + 0x000c; // 32 4kb buffer;
// ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &i);
i = 1;
ioctl (fd, SNDCTL_DSP_PROFILE, &i);
return 0;
}
void close_file (int fd)
{
// close the file pointed by file descriptor fd;
close (fd);
}
//------------------------------------------------------------------------------
// handle audio;
void display_AVCodecContext(AVCodecContext *pCodecCtx){
//
#define STDOUT stderr
fprintf(STDOUT, "pCodecCtx->bit_rate:%d/n", pCodecCtx->bit_rate);
fprintf(STDOUT, "pCodecCtx->sample_rate:%d/n", pCodecCtx->sample_rate);
fprintf(STDOUT, "pCodecCtx->channels:%d/n", pCodecCtx->channels);
fprintf(STDOUT, "pCodecCtx->frame_size:%d/n", pCodecCtx->frame_size);
fprintf(STDOUT, "pCodecCtx->frame_number:%d/n", pCodecCtx->frame_number);
fprintf(STDOUT, "pCodecCtx->delay:%d/n", pCodecCtx->delay);
fprintf(STDOUT, "pCodecCtx->frame_bits:%d/n", pCodecCtx->frame_bits);
}
// error if return -1;
// success if return 0;
// 這裏要用到指向指針的指針,否則傳不到值;
int av_init (char *file_name, AVFormatContext ** pFormatCtx,
AVCodecContext ** pCodecCtx, int *p_audioStream)
{
// init the codec and format of input file file_name;
int audioStream, i;
AVCodec *pCodec;
// catch error
assert(file_name != NULL);
assert(*pFormatCtx != NULL);
assert(*pCodecCtx != NULL);
// Register all formats and codecs
av_register_all ();
// open file
if (av_open_input_file (pFormatCtx, file_name, NULL, 0, NULL) != 0){
// Couldn't open file
fprintf (stderr, " Can't open %s!/n", file_name);
return -1;
}
// Retrieve stream information
if (av_find_stream_info (*pFormatCtx) < 0){
// Couldn't find stream information
return -1;
}
#ifdef AV_DEBUG
// Dump information about file onto standard error
dump_format (*pFormatCtx, 0, file_name, false);
#endif
// Find the first audio and video stream respectively
audioStream = -1;
for (i = 0; i < (*pFormatCtx)->nb_streams; i++){
if ((*pFormatCtx)->streams[i]->codec->codec_type ==
CODEC_TYPE_AUDIO)
{
audioStream = i;
}
}
#ifdef AV_DEBUG
// dump_stream_info(pFormatCtx);
#endif
// exclude error
if (audioStream == -1){
// Didn't find a audio or video stream
return -1;
}
// Get a pointer to the codec context for the audio stream
*pCodecCtx = (*pFormatCtx)->streams[audioStream]->codec;
// Find the decoder for the audio stream
pCodec = avcodec_find_decoder ((*pCodecCtx)->codec_id);
if (pCodec == NULL)
return -1; // Codec not found
// Open codec
if (avcodec_open ((*pCodecCtx), pCodec) < 0){
return -1; // Could not open codec
}
#ifdef AUDIO_DEBUG
// printf ("pCodecCtx->sample_rate:%d, audioStream:%d/n", (*pCodecCtx)->sample_rate, audioStream);
// display_AVCodecContext(*pCodecCtx);
#endif
*p_audioStream = audioStream;
return 0;
}
void av_play (AVFormatContext * pFormatCtx,
AVCodecContext * pCodecCtx, int audioStream)
{
// which was read from one frame;
AVPacket packet;
uint32_t len;
uint8_t decompressed_audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
int decompressed_audio_buf_size;
uint8_t * p_decompressed_audio_buf;
int fd = -1; // audio file or test file?
char filename[64] = "/dev/dsp";
int mode = O_WRONLY;
//
// open audio file or written file
// printf("fd:%d", fd);
fd = open_file(filename, mode);
// printf("fd:%d", fd);
//
set_audio(fd, pCodecCtx);
//
printf("(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2=%d/n", (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2);
printf("AVCODEC_MAX_AUDIO_FRAME_SIZE=%d/n", AVCODEC_MAX_AUDIO_FRAME_SIZE);
// for a test
// char test_file[256] = "my_pcm.pcm";
// fd = open_file(test_file, mode);
#ifdef AV_DEBUG
static int size = 0;
#endif
//
// set the sched priority
// 這是爲了提高音頻優先級;不曉得起作用沒;
int policy = SCHED_FIFO;
sched_setscheduler(0, policy, NULL);
int write_buf_size = 4196;
int written_size;
while (av_read_frame (pFormatCtx, &packet) >= 0)
{
// Is this a packet from the audio stream?
// 判斷是否音頻幀;
if (packet.stream_index == audioStream)
{
// Decode audio frame
// 解碼音頻數據爲pcm數據;
len = avcodec_decode_audio (pCodecCtx,
(int16_t *)decompressed_audio_buf,
&decompressed_audio_buf_size, // it is the decompressed frame in BYTES 解碼後的數據大小,字節爲單位;
packet.data,
packet.size );
// printf("len:%d, packet.size:%d/n", len, packet.size);
if ( len < 0 ){
// if error len = -1
printf("+----- error in decoding audio frame/n");
// exit(0);
}
// test lsosa
// printf("size = %d/n", size);
//******************************************************************
// 重點是這一部分,使用oss播放的代碼,之前的數據寫是否完整的問題就是出在這裏,或者是前面的set_audio函數設置不正確;
// audio_buf_info info;
p_decompressed_audio_buf = decompressed_audio_buf;
while ( decompressed_audio_buf_size > 0 ){
// 解碼後數據不爲零,則播放之,爲零,則;
written_size = write(fd, p_decompressed_audio_buf, decompressed_audio_buf_size);
if ( written_size == -1 ){
// printf("error:decompressed_audio_buf_size:%d, decompressed_audio_buf_size:%d, %s/n", /
decompressed_audio_buf_size, decompressed_audio_buf_size,strerror(errno));
// usleep(100);
continue;
}
// printf("decompressed_audio_buf_size:%d, written_size:%d/n", /
decompressed_audio_buf_size, written_size);
decompressed_audio_buf_size -= written_size;
p_decompressed_audio_buf += written_size;
}// end while
//******************************************************************
}
else
{
printf("+----- this is not audio frame/n");
}// end if
// Free the packet that was allocated by av_read_frame
av_free_packet (&packet);
}// end while of reading one frame;
close_file(fd);
}
void av_close (AVFormatContext * pFormatCtx, AVCodecContext * pCodecCtx)
{
// close the file and codec
// Close the codec
avcodec_close (pCodecCtx);
// Close the video file
av_close_input_file (pFormatCtx);
}
//------------------------------------------------------------------------------
int main (int argc, char **argv){
//
AVFormatContext *pFormatCtx;
int audioStream = -1;
AVCodecContext *pCodecCtx;
// exclude the error about args;
if ( argc != 2 ){
printf("please give a file name/n");
exit(0);
}
// 注意:這裏要用到指向指針的指針,是因爲這個初始化函數需要對指針的地址進行改動,
// 所以,只有這麼做,才能達到目的;
if ( av_init(argv[1], &pFormatCtx, &pCodecCtx, &audioStream) < 0 ){
//
fprintf(stderr, "error when av_init/n");
}
// play the audio file
av_play(pFormatCtx, pCodecCtx, audioStream);
// close all the opend files
av_close(pFormatCtx, pCodecCtx);
}
ffmpeg提取音頻播放器總結
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.