NDK 與 FFmpeg相關問題

1、ffmpeg 播放音視頻代碼流程

第一步 、 解封裝

1、avformat_alloc_context初始化一個AVFormatContext 結構體
2、avformat_open_input(&avFormatContext, data_source, 0, &dictionary) 打開一個媒體源
3、avformat_find_stream_info(avFormatContext, 0)找到媒體流的信息, 至此媒體文件的內容被封裝到了AVFormatContext 結構體中了;
4、avFormatContext->nb_streams 開啓遍歷,nb_streams代表AVFormatContext結構中的流的個數,一般有視頻流、音頻流 和字幕流
5、avFormatContext->streams[index_stream]獲取到AVFormatContext結構體中的一個流
6、avcodec_find_decoder(avCodecParameters->codec_id) 通過編碼器id找到對應的流的編解碼器AVCodec
7、avcodec_alloc_context3(avCodec) 通過一個編解碼器初始化出一個AVCodecContext
8、avcodec_parameters_to_context(avCodecContext, avCodecParameters)往編解碼器中設置流的參數
9、avcodec_open2(avCodecContext, avCodec, 0) 打開編解碼器,此時這個流的編解碼器和編解碼器上下文才可以真正使用了。

第二步 、 音視頻解碼

音視頻解碼的邏輯是每個對應的流中(音頻流 或者視頻流)定義兩個隊列(未解碼的AVPacket 和 已經解碼的AVFrame)

1、av_packet_alloc()初始化一個AVPacket,用來存放一個從編解碼器上下文中讀出來的一幀幀數據
2、av_read_frame(avFormatContext, avPacket) 從編解碼器上下文中讀一幀數據到AVPacket
3、packages.push(avPacket) 加入到隊列

4、packages.pop(avPacket) 從隊列中取出一個數據
5、AVFrame *avFrame = av_frame_alloc() 初始化一個AVFrame結構體
6、avcodec_receive_frame(avCodecContext, avFrame) 通過編解碼器上下文中解碼出一幀數據放到avFrame結構體中
7、frames.push(avFrame);解碼完成以後放入隊列

其中123步在一個線程中執行,4567步在另一個線程中執行

第三步 視頻的渲染

1、sws_getContext()初始化一個SwsContext結構體,參數解釋 第123個參數分別是被轉換源的寬高和格式第456個參數轉換後的寬高和格式 第7個參數是轉換所使用的算法 剩下三個參數全部設置成NULL
2、av_image_alloc(dst_data, dst_linesize, pContext->width, pContext->height, AV_PIX_FMT_ARGB, 1)給圖像申請內存,dst_data代表圖像通道、dst_linesize代表圖像每個通道的內存對齊的步長,即一行的對齊內存的寬度。
3、frames.pop(frame)從已解碼的隊列中取出一幀圖像
4、sws_scale(sws_ctx, frame->data, frame->linesize, 0, pContext->height, dst_data, dst_linesize) 講解碼的原始數據轉換成yuv數據
5、渲染到屏幕上

基於SurfaceView+ANativeWindow 渲染到屏幕

https://blog.csdn.net/byhook/article/details/84027283
1、ANativeWindow_fromSurface(env, surface)獲取到ANativeWindow 結構體
2、ANativeWindow_setBuffersGeometry(nativeWindow, width, height , WINDOW_FORMAT_RGBA_8888);設置窗口屬性
3、ANativeWindow_lock(nativeWindow, &windowBuffer, 0) 讀取到窗口buffer數據
4、修改數據

// 填數據到buffer,其實就是修改數據
uint8_t * dst_data = static_cast<uint8_t *>(windowBuffer.bits);
int lineSize = windowBuffer.stride * 4; // RGBA
// 下面就是逐行Copy了
for (int i = 0; i < windowBuffer.height; ++i) {
    // 一行Copy
    memcpy(dst_data + i * lineSize, src_data + i * src_liinesize, lineSize);
}

第四步 音頻的渲染

1、創建引擎並獲取引擎接口

1、slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
2、(*engineObject) ->Realize(engineObject, SL_BOOLEAN_FALSE)
3、(*engineObject) ->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface)

2、創建混音器

1、(*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0, 0, 0);
2、(*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE)
3、(*outputMixObject)->GetInterface(outputMixObject,SL_IID_ENVIRONMENTALREVERB,&outputMixEnvironmentalReverb)
4、設置

const SLEnvironmentalReverbSettings settings = SL_I3DL2_ENVIRONMENT_PRESET_DEFAULT;
(*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(outputMixEnvironmentalReverb, &settings);

3、創建播放器

1、配置輸入聲音信息

    // 創建buffer緩衝類型的隊列 2個隊列
    SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
                                                       2};
    // pcm數據格式
    // SL_DATAFORMAT_PCM:數據格式爲pcm格式
    // 2:雙聲道
    // SL_SAMPLINGRATE_44_1:採樣率爲44100(44.1赫茲 應用最廣的,兼容性最好的)
    // SL_PCMSAMPLEFORMAT_FIXED_16:採樣格式爲16bit (16位)(2個字節)
    // SL_PCMSAMPLEFORMAT_FIXED_16:數據大小爲16bit (16位)(2個字節)
    // SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT:左右聲道(雙聲道)  (雙聲道 立體聲的效果)
    // SL_BYTEORDER_LITTLEENDIAN:小端模式
    SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1,
                                   SL_PCMSAMPLEFORMAT_FIXED_16,
                                   SL_PCMSAMPLEFORMAT_FIXED_16,
                                   SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
                                   SL_BYTEORDER_LITTLEENDIAN};

    // 數據源 將上述配置信息放到這個數據源中
    SLDataSource audioSrc = {&loc_bufq, &format_pcm};

2、配置音軌(輸出

    // 設置混音器
    SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&loc_outmix, NULL};

    //  需要的接口 操作隊列的接口
    const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
    const SLboolean req[1] = {SL_BOOLEAN_TRUE};

3、創建播放器 CreateAudioPlayer(engineInterface, &bqPlayerObject, &audioSrc, &audioSnk, 1, ids, req);
4、初始化播放器 (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE)
5、獲取播放器接口 (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay)
6、設置播放回調函數

    //  獲取播放器隊列接口:SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue);

    //  設置回調 
    void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
    (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);

7、設置播放器狀態爲播放狀態 並手動激活回調函數

  // 設置播放器狀態爲播放狀態
  (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);

  // 手動激活回調函數
  bqPlayerCallback(bqPlayerBufferQueue, this);

8、回調函數編寫(將pcm原始數據重採樣)
1、定義分配重採樣上下文

    out_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); // 通道數   AV_CH_LAYOUT_STEREO(雙聲道的意思)
    out_sample_size = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
    out_sample_rate = 44100; // 採樣率
    out_buffers_size = out_sample_rate * out_sample_size * out_channels;

    out_buffers = static_cast<uint8_t *>(malloc(out_buffers_size));
    memset(out_buffers, 0, out_buffers_size);

    // swr_ctx = swr_alloc();
    swr_ctx = swr_alloc_set_opts(0, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, out_sample_rate,
              pContext->channel_layout, pContext->sample_fmt, pContext->sample_rate, 0 ,0);

    swr_init(swr_ctx);

2、計算PCM數據大小

int AudioChannel::getPCM() {
    int pcm_data_size = 0;

    // PCM 在 frames 隊列中
    AVFrame * frame = 0;
    while(isPlaying) {
        int ret = frames.pop(frame);

        // 如果停止播放,跳出循環, 出了循環,就要釋放
        if (!isPlaying) {
            break;
        }

        if (!ret) {
            continue;
        }

        // PCM 的處理邏輯
        frame->data;

        // 音頻播放器的數據格式是我們在下面定義的(16位 雙聲道 ....)
        // 而原始數據(是待播放的音頻PCM數據)
        // 所以,上面的兩句話,無法統一,一個是(自己定義的16位 雙聲道 ..) 一個是原始數據,爲了解決上面的問題,就需要重採樣。
        // 開始重採樣
        int dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, frame->sample_rate) +
                                        frame->nb_samples, out_sample_rate, frame->sample_rate, AV_ROUND_UP);

        ret = swr_convert(swr_ctx, &out_buffers, dst_nb_samples, (const uint8_t **) frame->data, frame->nb_samples);
        if (ret < 0) {
            fprintf(stderr, "Error while converting\n");
        }

        // pcm_data_size = ret * out_sample_size; // 每個聲道的數據
        pcm_data_size = ret * out_sample_size * out_channels;

        break;
    } // end while
    releaseAVFrame(&frame); // 渲染完了,frame沒用了,釋放掉
    return pcm_data_size;
}

3、播放數據(*bq)->Enqueue(bq, audioChannel->out_buffers , pcmSize);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章