FFMpeg 提取音頻播放器總結

fmms 12年前發布 | 98K 次閱讀 FFmpeg 多媒體處理

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 <avcodec.h> 
#include <avformat.h> 
#include <avutil.h> 
#include <assert.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <X11/Xlib.h> 
#include <sys/soundcard.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <sys/ioctl.h> 
#include <unistd.h> 
#include <errno.h> 
#include <string.h> 
#include <sched.h> 

#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->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); 

} 


http://weiyuhu.iteye.com/blog/576610

 本文由用戶 fmms 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!