FFmpeg進行音頻的解碼和播放

回顧知識點

FFmpeg4.0.2編譯32位和64位動態(tài)庫
FFmpeg 內(nèi)容介紹 音視頻解碼和播放

音頻編碼

音頻數(shù)字化主要有壓縮與非壓縮(pcm)兩種方式。

  • 非壓縮編碼(PCM)PCM音頻編碼
    PCM通過抽樣、量化、編碼三個步驟將連續(xù)變化的模擬信號轉(zhuǎn)換為數(shù)字編碼。
    當(dāng)采樣頻率fs.max大于信號中最高頻率fmax的2倍時(fs.max>2fmax),采樣之后的數(shù)字信號完整地保留了原始信號中的信息,一般實際應(yīng)用中保證采樣頻率為信號最高頻率的2.56~4倍;采樣定理又稱奈奎斯特定理。
    PCM信號未經(jīng)過任何編碼和壓縮處理, 聲音之所以能夠數(shù)字化,是因為人耳所能聽到的聲音頻率不是無限寬的,主要在20kHz以上。按照抽樣定理,只有抽樣頻率大于40kHz,才能無失真地重建原始聲音。如CD采用44.1kHz的抽樣頻率,其他則主要采用48kHz或96kHz。

  • 壓縮編碼
    PCM雖然為無損壓縮,但由典型的音頻信號表示的信號特性沒有達到最佳,也沒有很好的適應(yīng)人耳聽覺系統(tǒng)的特定要求。PCM的數(shù)據(jù)量過高,從而造成存儲和傳輸方面的障礙,因此必須使用相應(yīng)的技術(shù)降低數(shù)字信號源的數(shù)據(jù)率,又盡可能不對節(jié)目造成損傷,這就是壓縮技術(shù)
    常見的壓縮的音頻格式WAV,MP3。
    WAV格式,是微軟公司開發(fā)的一種聲音文件格式,也叫波形聲音文件,是最早的數(shù)字音頻格式,被Windows平臺及其應(yīng)用程序廣泛支持,壓縮率低。
    MP3全稱是MPEG-1 Audio Layer 3,它在1992年合并至MPEG規(guī)范中。MP3能夠以高音質(zhì)、低采樣率對數(shù)字音頻文件進行壓縮。應(yīng)用最普遍。

FFmpeg 解碼音頻文件

上一篇FFmpeg 內(nèi)容介紹 音視頻解碼和播放 介紹了FFmpeg進行解碼的常見函數(shù)和,解碼的過程。相關(guān)的函數(shù)介紹忘記了,可以參考上一篇。

  • 直接核心貼代碼
    實現(xiàn)功能:將mp3、wav等格式轉(zhuǎn)成pcm
    // 源文件路徑
    const char * src_path = env->GetStringUTFChars(src_audio_path, NULL);
    // 生產(chǎn)的pcm文件路徑
    const char * dst_path = env->GetStringUTFChars(dst_audio_path, NULL);
    // AVFormatContext 對象創(chuàng)建
    AVFormatContext *avFormatContext = avformat_alloc_context();
   // 打開音頻文件
    int ret = avformat_open_input(&avFormatContext, src_path, NULL, NULL);
    if(ret != 0) {
        LOGE("打開文件失敗");
        return;
    }
    // 輸出音頻文件的信息
    av_dump_format(avFormatContext, 0, src_path, 0);
   // 獲取音頻文件的流信息
    ret = avformat_find_stream_info(avFormatContext, NULL);
    if(ret < 0) {
        LOGE("獲取流信息失敗");
        return;
    }
    // 查找音頻流在文件的所有流集合中的位置
    int streamIndex = 0;
    for (int i = 0; i < avFormatContext->nb_streams; ++i) {
        enum AVMediaType avMediaType = avFormatContext->streams[i]->codecpar->codec_type;
        if(avMediaType == AVMEDIA_TYPE_AUDIO) {  //這邊和視頻不一樣,是AUDIO
            streamIndex = i;
        }
    }
    // 拿到對應(yīng)音頻流的參數(shù)
    AVCodecParameters *avCodecParameters = avFormatContext->streams[streamIndex]->codecpar;
    // 獲取解碼器的標(biāo)識ID
    enum AVCodecID avCodecId = avCodecParameters->codec_id;
    // 通過獲取的ID,獲取對應(yīng)的解碼器
    AVCodec *avCodec = avcodec_find_decoder(avCodecId);
    // 創(chuàng)建一個解碼器上下文對象
    AVCodecContext *avCodecContext = avcodec_alloc_context3(NULL);
    if (avCodecContext == NULL) {
        //創(chuàng)建解碼器上下文失敗
        LOGE("創(chuàng)建解碼器上下文失敗");
        return;
    }
    // 將新的API中的 codecpar 轉(zhuǎn)成 AVCodecContext
    avcodec_parameters_to_context(avCodecContext, avCodecParameters);
    ret = avcodec_open2(avCodecContext, avCodec, NULL);
    if (ret < 0) {
        LOGE("打開解碼器失敗 ");
        return;
    }
    LOGE("decodec name: %s", avCodec->name);

    //壓縮數(shù)據(jù)包
    AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    //解壓縮后存放的數(shù)據(jù)幀的對象
    AVFrame *inFrame = av_frame_alloc();
    //frame->16bit 44100 PCM 統(tǒng)一音頻采樣格式與采樣率
    //創(chuàng)建swrcontext上下文件
    SwrContext *swrContext = swr_alloc();
    //音頻格式  輸入的采樣設(shè)置參數(shù)
    AVSampleFormat inFormat = avCodecContext->sample_fmt;
    // 出入的采樣格式
    AVSampleFormat  outFormat = AV_SAMPLE_FMT_S16;
    // 輸入采樣率
    int inSampleRate = avCodecContext->sample_rate;
    // 輸出采樣率
    int outSampleRate = 44100;
    // 輸入聲道布局
    uint64_t in_ch_layout = avCodecContext->channel_layout;
    //輸出聲道布局
    uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
    //給Swrcontext 分配空間,設(shè)置公共參數(shù)
    swr_alloc_set_opts(swrContext, out_ch_layout, outFormat, outSampleRate,
            in_ch_layout, inFormat, inSampleRate, 0, NULL
            );
    // 初始化
    swr_init(swrContext);
    // 獲取聲道數(shù)量
    int outChannelCount = av_get_channel_layout_nb_channels(out_ch_layout);

    int currentIndex = 0;
    LOGE("聲道數(shù)量%d ", outChannelCount);
    // 設(shè)置音頻緩沖區(qū)間 16bit   44100  PCM數(shù)據(jù), 雙聲道
    uint8_t *out_buffer = (uint8_t *) av_malloc(2 * 44100);
    // 創(chuàng)建pcm的文件對象
    FILE *fp_pcm = fopen(dst_path, "wb");
    //開始讀取源文件,進行解碼
    while (av_read_frame(avFormatContext, packet) >= 0) {
        if (packet->stream_index == streamIndex) {
            avcodec_send_packet(avCodecContext, packet);
            //解碼
            ret = avcodec_receive_frame(avCodecContext, inFrame);
            if(ret == 0) {
                //將每一幀數(shù)據(jù)轉(zhuǎn)換成pcm
                swr_convert(swrContext, &out_buffer, 2 * 44100,
                            (const uint8_t **) inFrame->data, inFrame->nb_samples);
                //獲取實際的緩存大小
                int out_buffer_size=av_samples_get_buffer_size(NULL, outChannelCount, inFrame->nb_samples, outFormat, 1);
                // 寫入文件
                fwrite(out_buffer, 1, out_buffer_size,fp_pcm);
            }
            LOGE("正在解碼%d", currentIndex++);
        }
    }
    // 及時釋放
    fclose(fp_pcm);
    av_frame_free(&inFrame);
    av_free(out_buffer);
    swr_free(&swrContext);
    avcodec_close(avCodecContext);
    avformat_close_input(&avFormatContext);

    env->ReleaseStringUTFChars(src_audio_path, src_path);
    env->ReleaseStringUTFChars(dst_audio_path, dst_path);

利用FFmpeg和原生的AudioTrack 進行播放

思路:由FFmpeg進行解碼,將解碼后的數(shù)據(jù)再通過jni傳到Java中的audioTrack對象進行播放

  • 創(chuàng)建AudioTrack對象
public class AudioPlayer {

  private AudioTrack audioTrack;

  public AudioPlayer() {

  }

  public void play(final String audioPath) {
    new Thread(new Runnable() {
      @Override
      public void run() {
        nativePlay(audioPath);
      }
    }).start();

  }

  public native void nativePlay(String audioPath);

  /**
   * 這個方法是給C++ 調(diào)用的, 在ffmpeg獲取的音頻頻率和通道數(shù)來調(diào)用原生的openSl的音頻播放
   *
   * @param sampleRate   音頻文件的頻率
   * @param channelCount 通道數(shù)
   */
  public void createAudio(int sampleRate, int channelCount) {
     //通過通道數(shù)來判斷是單聲道還是立體聲
    int channelConfig;
    if (channelCount == 1) {
      channelConfig = AudioFormat.CHANNEL_OUT_MONO;
    } else if (channelCount == 2) {
      channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
    } else {
      channelConfig = AudioFormat.CHANNEL_OUT_MONO;
    }

    //獲取實際的緩存大小
    int bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig,
        AudioFormat.ENCODING_PCM_16BIT);
    audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig,
        AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM);
    audioTrack.play();
  }

  public synchronized void playTrack(byte[] buffer, int length) {
    if(audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
      //將ffmpeg解析出來而定音頻數(shù)據(jù),寫入到open es中
      audioTrack.write(buffer, 0, length);
    }
  }
}
  • FFmpeg的解碼
const char* src_path = env->GetStringUTFChars(audio_path, NULL);

    AVFormatContext *avFormatContext = avformat_alloc_context();
    int ret = avformat_open_input(&avFormatContext, src_path, NULL, NULL);
    if(ret != 0) {
        LOGE("打開文件失敗");
        return;
    }

    av_dump_format(avFormatContext, 0, src_path, 0);

    ret = avformat_find_stream_info(avFormatContext, NULL);
    if(ret < 0) {
        LOGE("獲取流信息失敗");
        return;
    }

    int streamIndex = 0;
    for (int i = 0; i < avFormatContext->nb_streams; ++i) {
        enum AVMediaType avMediaType = avFormatContext->streams[i]->codecpar->codec_type;
        if(avMediaType == AVMEDIA_TYPE_AUDIO) {
            streamIndex = i;
        }
    }

    AVCodecParameters *avCodecParameters = avFormatContext->streams[streamIndex]->codecpar;
    enum AVCodecID avCodecId = avCodecParameters->codec_id;

    AVCodec *avCodec = avcodec_find_decoder(avCodecId);

    AVCodecContext *avCodecContext = avcodec_alloc_context3(NULL);
    if (avCodecContext == NULL) {
        //創(chuàng)建解碼器上下文失敗
        LOGE("創(chuàng)建解碼器上下文失敗");
        return;
    }
    // 將新的API中的 codecpar 轉(zhuǎn)成 AVCodecContext
    avcodec_parameters_to_context(avCodecContext, avCodecParameters);
    ret = avcodec_open2(avCodecContext, avCodec, NULL);
    if (ret < 0) {
        LOGE("打開解碼器失敗 ");
        return;
    }
    LOGE("decodec name: %s", avCodec->name);

    //壓縮數(shù)據(jù)
    AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    //解壓縮數(shù)據(jù)
    AVFrame *inFrame = av_frame_alloc();
    //frame->16bit 44100 PCM 統(tǒng)一音頻采樣格式與采樣率
    //創(chuàng)建swrcontext上下文件
    SwrContext *swrContext = swr_alloc();
    //音頻格式  輸入的采樣設(shè)置參數(shù)
    AVSampleFormat inFormat = avCodecContext->sample_fmt;
    // 出入的采樣格式
    AVSampleFormat  outFormat = AV_SAMPLE_FMT_S16;
    // 輸入采樣率
    int inSampleRate = avCodecContext->sample_rate;
    // 輸出采樣率
    int outSampleRate = 44100;
    // 輸入聲道布局
    uint64_t in_ch_layout = avCodecContext->channel_layout;
    //輸出聲道布局
    uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
    //給Swrcontext 分配空間,設(shè)置公共參數(shù)
    //struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
    //                                      int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
    //                                      int64_t  in_ch_layout, enum AVSampleFormat  in_sample_fmt, int  in_sample_rate,
    //                                      int log_offset, void *log_ctx);
    swr_alloc_set_opts(swrContext, out_ch_layout, outFormat, outSampleRate,
                       in_ch_layout, inFormat, inSampleRate, 0, NULL
    );
    // 初始化
    swr_init(swrContext);
    // 獲取聲道數(shù)量
    int outChannelCount = av_get_channel_layout_nb_channels(out_ch_layout);
    //獲取native方法所在的Java的類
    jclass jclz = env->GetObjectClass(instance);
    //獲取方法createAudio的id
    jmethodID create_method_id = env->GetMethodID(jclz, "createAudio", "(II)V");
    // 調(diào)用createAudio的Java方法
    env->CallVoidMethod(instance, create_method_id, outSampleRate, outChannelCount);
    // 獲取方法playTrack的id
    jmethodID play_method_id = env->GetMethodID(jclz, "playTrack", "([BI)V");
    // 設(shè)置音頻緩沖區(qū)間 16bit   44100  PCM數(shù)據(jù), 雙聲道
    uint8_t *out_buffer = (uint8_t *) av_malloc(2 * 44100);
    int frameCount = 0;
    while (av_read_frame(avFormatContext, packet) >= 0) {
        if (packet->stream_index == streamIndex) {
            avcodec_send_packet(avCodecContext, packet);
            //解碼
            ret = avcodec_receive_frame(avCodecContext, inFrame);
            if (ret == 0) {
                frameCount ++;
                LOGE("解碼 %d",frameCount);
                /**
                 * int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,
                                const uint8_t **in , int in_count);
                 */
                swr_convert(swrContext, &out_buffer, 44100 * 2, (const uint8_t **) inFrame->data, inFrame->nb_samples);
//                緩沖區(qū)的大小
                int size = av_samples_get_buffer_size(NULL, outChannelCount, inFrame->nb_samples,
                                                      AV_SAMPLE_FMT_S16, 1);
                //數(shù)據(jù)轉(zhuǎn)換
                jbyteArray audio_sample_array = env->NewByteArray(size);
                env->SetByteArrayRegion(audio_sample_array, 0, size, (const jbyte *) out_buffer);
                // 調(diào)用將數(shù)據(jù)寫入到audiotrack,進行播放
                env->CallVoidMethod(instance, play_method_id, audio_sample_array, size);
                env->DeleteLocalRef(audio_sample_array);

            }
        }
    }

結(jié)語

以上就是利用FFmpeg對音頻文件進行解碼以及播放的內(nèi)容,如果有錯誤,歡迎大家指正出來

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容