国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Android FFmpeg 解碼 OpenSL ES 播放音頻

這篇具有很好參考價(jià)值的文章主要介紹了Android FFmpeg 解碼 OpenSL ES 播放音頻。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

?在Android開發(fā)中,OpenSLES(Open Sound Library for Embedded Systems)是一個(gè) C/C++ 音頻庫(kù),提供了底層的音頻功能和處理接口。它是 Android 平臺(tái)上用于實(shí)現(xiàn)低延遲和高性能音頻功能的一種選擇。

本文的主線任務(wù)是描述 一個(gè)媒體文件通過(guò) FFmpeg 解碼后用 OpenSL ES 播放音頻的過(guò)程

因?yàn)榇a量很多,所以我直接從 Native 層開始了,看不懂的可以下載源代碼配合著看(末尾)


extern "C"
JNIEXPORT void JNICALL
Java_cn_wk_opensl_1demo_MainActivity_audioPlayer(JNIEnv *env, jobject thiz, jstring dataStr) {
    const char *dataSource = env->GetStringUTFChars(dataStr, nullptr);

    pthread_create(&pid_prepare, nullptr, task_prepare, (void *) dataSource);
}

這是 JNI 函數(shù),上層傳遞媒體文件全路徑到 Native 層(因?yàn)?FFmpeg 讀取文件需要),之后開啟準(zhǔn)備線程(就是要開始進(jìn)行異步對(duì)文件做處理了)

/**
 * FFmpeg 對(duì)媒體文件 做處理
 */
void *task_prepare(void *args) {
    const char *data_source = (const char *) args;
    LOGI("data_source: %s", data_source)

    formatContext = avformat_alloc_context(); // 給 媒體上下文 開辟內(nèi)存
    av_dict_set(&dictionary, "timeout", "5000000", 0); // 設(shè)置字典參數(shù)

    // TODO 打開媒體地址(如:文件路徑,直播地址rtmp等)
    avformat_open_input(&formatContext, data_source, nullptr, &dictionary);
    // 釋放字典(用完就釋放)
    av_dict_free(&dictionary);

    // TODO 查找媒體中的音視頻流的信息
    avformat_find_stream_info(formatContext, nullptr);

    // TODO 根據(jù)流信息,把 音頻流、視頻流 分開處理
    for (int stream_index = 0; stream_index < formatContext->nb_streams; ++stream_index) {
        AVStream *stream = formatContext->streams[stream_index];      // 獲取媒體流(視頻,音頻)
        AVCodecParameters *parameters = stream->codecpar;             // 從流中獲取 編解碼 參數(shù)
        AVCodec *codec = avcodec_find_decoder(parameters->codec_id);  // 獲取編解碼器
        AVCodecContext *codecContext = avcodec_alloc_context3(codec); // 給 codecContext 開辟內(nèi)存
        avcodec_parameters_to_context(codecContext, parameters);      // codecContext 初始化
        avcodec_open2(codecContext, codec, nullptr);          // 打開 編解碼器

        // 從編解碼參數(shù)中,區(qū)分流的類型,分別處理 (codec_type == 音頻流/視頻流/字幕流)
        if (parameters->codec_type == AVMediaType::AVMEDIA_TYPE_AUDIO) {
            LOGI("音頻流")
            audio_channel = new AudioChannel(codecContext); // codecContext 才是真正干活的
            audio_channel->start(); // 開啟 解碼線程 和 播放線程

            pthread_create(&pid_start, nullptr, task_start, nullptr); // 數(shù)據(jù)傳輸線程
        } else if (parameters->codec_type == AVMediaType::AVMEDIA_TYPE_VIDEO) {
            LOGI("視頻流")
        } else if (parameters->codec_type == AVMediaType::AVMEDIA_TYPE_SUBTITLE) {
            LOGI("字幕流")
        }
    }
    return nullptr; // 函數(shù)的返回值是 void* 必須返回 nullptr
}

總結(jié):把文件路徑給到 FFmpeg讀取媒體文件,讀取后對(duì)媒體文件的流分開操作(視頻搞視頻的,音頻搞音頻的),這里音頻處理封裝了 AudioChannel 這個(gè)類。

需要注意的是,我為了盡可能減少代碼,省略了很多 FFmpeg 函數(shù)的返回值,比如 avcodec_parameters_to_context() 和?avcodec_open2() 都是有返回值的,非0為失敗,可以自行對(duì)錯(cuò)誤做處理


可以看到又開啟了一個(gè)線程:task_start

/**
 * 將 AVPacket 傳給 AudioChannel
 */
void *task_start(void *args) {
    while (1) {
        if (audio_channel && audio_channel->packets.size() > 100) {
            av_usleep(10 * 1000); // FFmpeg 的時(shí)間是微秒,所以這個(gè)是10毫秒
            continue;
        }
        AVPacket *packet = av_packet_alloc();             // 給 AVPacket 開辟內(nèi)存
        int ret = av_read_frame(formatContext, packet);   // 從 formatContext 讀幀賦值到 AVPacket
        if (!ret) {
            audio_channel->packets.insertToQueue(packet);
        } else {
            break;
        }
    }
    return nullptr;
}

工具線程:將 FFmpeg 讀取到的?AVPacket 傳給 AudioChannel 而已


重頭戲:AudioChannel

void AudioChannel::start() {
    isPlaying = 1;

    // 隊(duì)列開始工作
    packets.setWork(1);
    frames.setWork(1);

    // 音頻解碼線程
    pthread_create(&pid_audio_decode, nullptr, task_audio_decode, this);
    // 音頻播放線程
    pthread_create(&pid_audio_play, nullptr, task_audio_play, this);
}

開啟兩個(gè)線程:一個(gè)解碼線程,一個(gè)播放線程(FFmpeg 解碼,OpenSL ES 播放)

void *task_audio_decode(void *args) {  // 很頭痛,C的子線程函數(shù)必須是這個(gè)格式,所以要包裝一層....
    auto *audio_channel = static_cast<AudioChannel *>(args);
    audio_channel->audio_decode();
    return nullptr;
}

/**
 * 音頻解碼:codecContext 把 AVPacket 解碼為 AVFrame
 */
void AudioChannel::audio_decode() {
    AVPacket *pkt = nullptr;
    while (isPlaying) {
        if (isPlaying && frames.size() > 100) {
            av_usleep(10 * 1000);
            continue;
        }
        int ret = packets.getQueueAndDel(pkt);
        if (!ret) {
            continue; // 生產(chǎn)-消費(fèi)模型,所以可能會(huì)失敗,重來(lái)就行
        }
        // TODO 把 AVPacket 給 codecContext 解碼
        ret = avcodec_send_packet(codecContext, pkt);

        AVFrame *frame = av_frame_alloc(); // 給 AVFrame 開辟內(nèi)存

        // TODO 從 codecContext 中拿解碼后的產(chǎn)物 AVFrame
        ret = avcodec_receive_frame(codecContext, frame);

        if (ret == AVERROR(EAGAIN))
            continue; // 音頻幀可能獲取失敗,重新拿一次

        // 原始包 AVFrame 加入播放隊(duì)列
        frames.insertToQueue(frame);
    }
}

總結(jié):FFmpeg 將 AVPacket 解碼為 AVFrame 并塞進(jìn)播放隊(duì)列

接下來(lái)是播放線程:

void *task_audio_play(void *args) {  // 頭痛頭痛
    auto *audio_channel = static_cast<AudioChannel *>(args);
    audio_channel->audio_play();
    return nullptr;
}

/**
 * 音頻播放
 */
void AudioChannel::audio_play() {
    SLresult result; // 用于接收 執(zhí)行成功或者失敗的返回值

    // TODO 創(chuàng)建引擎對(duì)象并獲取【引擎接口】
    slCreateEngine(&engineObject, 0, 0,
                   0, 0, 0);
    (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface);

    // TODO 創(chuàng)建、初始化混音器
    (*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0, 0, 0);
    (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);

    // TODO 創(chuàng)建并初始化播放器
    SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 10};
    SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, // PCM數(shù)據(jù)格式
                                   2, // 聲道數(shù)
                                   SL_SAMPLINGRATE_44_1, // 采樣率(每秒44100個(gè)點(diǎn))
                                   SL_PCMSAMPLEFORMAT_FIXED_16, // 每秒采樣樣本 存放大小 16bit
                                   SL_PCMSAMPLEFORMAT_FIXED_16, // 每個(gè)樣本位數(shù) 16bit
                                   SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, // 前左聲道  前右聲道
                                   SL_BYTEORDER_LITTLEENDIAN};
    SLDataSource audioSrc = {&loc_bufq, &format_pcm};
    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};
    (*engineInterface)->CreateAudioPlayer(engineInterface, &bqPlayerObject, &audioSrc, &audioSnk, 1, ids, req);
    (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);

    // TODO 設(shè)置回調(diào)函數(shù)
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue);
    (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);

    // TODO 設(shè)置播放狀態(tài)
    (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);

    // 6.手動(dòng)激活回調(diào)函數(shù)
    bqPlayerCallback(bqPlayerBufferQueue, this);
}

怎么樣?頭痛嗎,我也很頭痛,代碼量真的挺多的(我還把返回值去掉了的),這個(gè) OpenSL ES 的使用真的沒(méi)十年腦血栓設(shè)計(jì)不出來(lái),其實(shí)基本上都是樣板代碼,大概知道什么意思就行了,關(guān)鍵是回調(diào)函數(shù):

/**
 * 真正播放的函數(shù),這個(gè)函數(shù)會(huì)一直調(diào)用
 * 關(guān)鍵是 SLAndroidSimpleBufferQueueItf 這個(gè)結(jié)構(gòu)體,往這個(gè)結(jié)構(gòu)體的隊(duì)列加 PCM 數(shù)據(jù)就能播放了
 */
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *args) {
    auto *audio_channel = static_cast<AudioChannel *>(args);
    int pcm_size = audio_channel->getPCM();

    (*bq)->Enqueue(bq, audio_channel->out_buffers, pcm_size);
}

?播放音頻的關(guān)鍵就是這個(gè),往 Enqueue 上加 PCM 數(shù)據(jù)就能播放了


本篇文章僅實(shí)現(xiàn)了 FFmpeg 和 OpenGL ES 配和播放媒體文件音頻的功能,其中有非常多的細(xì)節(jié)沒(méi)有去完善(比如函數(shù)錯(cuò)誤返回值的處理、內(nèi)存泄漏等等),因?yàn)?strong>我為了更好的閱讀和理解 FFmpeg 和 OpenSL ES,對(duì)非主線代碼做了刪減,所以讀者可以自行添加

源代碼鏈接:https://github.com/yinwokang/Android-OpenSLES/文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-481991.html

到了這里,關(guān)于Android FFmpeg 解碼 OpenSL ES 播放音頻的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • ffmpeg學(xué)習(xí)之音頻解碼數(shù)據(jù)

    ffmpeg學(xué)習(xí)之音頻解碼數(shù)據(jù)

    音頻數(shù)據(jù)經(jīng)過(guò)解碼后會(huì)被保存為,pcm數(shù)據(jù)格式。而對(duì)應(yīng)的處理流程如下所示。 avcodec_find_encoder()? avcodec_find_encoder_by_name()? avcodec_alloc_context3()? ?設(shè)置對(duì)應(yīng)音頻編碼的數(shù)據(jù)類型 設(shè)置編碼的frame的相關(guān)參數(shù) 整個(gè)代碼:

    2024年02月16日
    瀏覽(18)
  • FFmpeg 解碼 AAC 格式的音頻

    FFmpeg 解碼 AAC 格式的音頻

    FFmpeg 默認(rèn)是可以解碼 AAC 格式的音頻,但是如果需要獲取 PCM16 此類數(shù)據(jù)則需要經(jīng)過(guò)音頻轉(zhuǎn)碼。首先要打開解碼器,然后向解碼器發(fā)送 AAC 音頻幀(不帶 ADTS),然后從解碼器獲取解碼后的音頻幀,數(shù)據(jù)是 float 類型的,如果需要?jiǎng)t進(jìn)行轉(zhuǎn)碼流程將 float 轉(zhuǎn)成整型。 一、AAC 音頻

    2024年02月11日
    瀏覽(23)
  • FFmpeg音頻解碼流程詳解及簡(jiǎn)單demo參考

    FFmpeg音頻解碼流程詳解及簡(jiǎn)單demo參考

    ????????本文主要講解FFmpeg的音頻解碼具體流程,API使用。最后再以一個(gè)非常簡(jiǎn)單的demo演示將一個(gè)mp3格式的音頻文件解碼為原始數(shù)據(jù)pcm文件。?本文主要基于FFmpeg音頻解碼新接口。 ???API接口簡(jiǎn)單大體講解如下: ????????這一步是ffmpeg的任何程序的第一步都是需要先注

    2023年04月08日
    瀏覽(70)
  • Android 音視頻開發(fā)—MediaPlayer音頻與視頻的播放介紹

    Android 音視頻開發(fā)—MediaPlayer音頻與視頻的播放介紹

    Android多媒體中的——MediaPlayer,我們可以通過(guò)這個(gè)API來(lái)播放音頻和視頻該類是Androd多媒體框架中的一個(gè)重要組件,通過(guò)該類,我們可以以最小的步驟來(lái)獲取,解碼和播放音視頻。 它支持三種不同的媒體來(lái)源: 本地資源 內(nèi)部的URI,比如你可以通過(guò)ContentResolver來(lái)獲取 外部URL(流

    2024年02月10日
    瀏覽(27)
  • 利用ffmpeg和opencv進(jìn)行視頻的解碼播放

    引子 OpenCV中有自己的用于處理圖片和視頻的類VideoCapture,可以很方便的讀入文件和顯示。 現(xiàn)在視頻數(shù)據(jù)流是ffmpeg解碼h264文件得到的,由于要依賴該數(shù)據(jù)源進(jìn)行相應(yīng)的后續(xù)處理,所以需要將ffmpeg中得到的數(shù)據(jù)緩存轉(zhuǎn)換成可以被OpenCV處理的Mat類對(duì)象。 ffmpeg介紹 FFmpeg是一個(gè)開源

    2024年02月13日
    瀏覽(30)
  • 深入淺出:FFmpeg 音頻解碼與處理AVFrame全解析

    深入淺出:FFmpeg 音頻解碼與處理AVFrame全解析

    FFmpeg 是一個(gè)開源的音視頻處理軟件,它包含了一系列的庫(kù)和程序,用于處理音頻、視頻和其他多媒體數(shù)據(jù)。FFmpeg 的名字來(lái)源于 “Fast Forward MPEG”,其中 MPEG 是一種常見的音視頻編碼標(biāo)準(zhǔn)。 FFmpeg 項(xiàng)目于 2000 年由 Fabrice Bellard 啟動(dòng),他是 QEMU(一種開源的計(jì)算機(jī)模擬器和虛擬機(jī)

    2024年02月04日
    瀏覽(41)
  • 基于FFMpeg實(shí)現(xiàn)音頻mp3/aac/wav解碼

    編譯環(huán)境:Ubuntu16.04 64位 交叉編譯工具:arm-himix200-linux-gcc 我這里使用的是ffmpeg-5.1.2.tar.gz,下載地址點(diǎn)擊下載地址。 這樣,/root/ffmpeg-5.1.2/output下面就是咱們要的程序,bin目錄下ffmpeg可以在開發(fā)板上運(yùn)行,include下是需要的頭文件,lib下是需要的靜態(tài)庫(kù),share/ffmpeg/examples是一些

    2024年02月11日
    瀏覽(93)
  • ExoPlayer(AndroidX Media3) 擴(kuò)展ffmpeg實(shí)現(xiàn)音頻軟解碼

    ExoPlayer(AndroidX Media3) 擴(kuò)展ffmpeg實(shí)現(xiàn)音頻軟解碼

    1.Ubuntu 20.04.4 LTS 2.AndroidNDK版本r26C 3.AndroidStudio 2023.1.1(配置好SDK和JDK 17.0.10) 4.ffmpeg6.0源碼 5.ExoPlayer源碼,AndroidX Media release分支版本 目前官方已廢棄Exopler2,代碼已經(jīng)遷移到AndroidX Media,下載完成設(shè)置FFMPEG_MODULE_PATH變量 1. git clone https://github.com/androidx/media 2. cd media FFMPEG_MODULE_PATH

    2024年04月12日
    瀏覽(161)
  • 【Qt+FFmpeg】鼠標(biāo)滾輪放大、縮小、移動(dòng)——解碼播放本地視頻(三)

    【Qt+FFmpeg】鼠標(biāo)滾輪放大、縮小、移動(dòng)——解碼播放本地視頻(三)

    ?上一期我們實(shí)現(xiàn)了播放、暫停、重播、倍速功能,這期來(lái)談?wù)勅绾螌?shí)現(xiàn)鼠標(biāo)滾輪放大縮小和移動(dòng);如果還沒(méi)看過(guò)上期,請(qǐng)移步 【Qt+FFmpeg】解碼播放本地視頻(一)_logani的博客-CSDN博客【Qt+FFmpeg】解碼播放本地視頻(二)——實(shí)現(xiàn)播放、暫停、重播、倍速功能_logani的博客-C

    2024年02月10日
    瀏覽(19)
  • ES8311 - 音頻編解碼芯片調(diào)試

    ES8311 - 音頻編解碼芯片調(diào)試

    目錄 前言 ES8311 codec芯片介紹 調(diào)通的配置 軟件配置 ID?驗(yàn)證 回環(huán)測(cè)試 注意事項(xiàng): 最近因任務(wù)需求,需要將一款codec?芯片配合TTS調(diào)通,做某款云喇叭播放設(shè)備。 這款codec的驅(qū)動(dòng)已經(jīng)適配過(guò)多次,但在此系統(tǒng)上卻是第一次調(diào)試,謹(jǐn)做記錄。 System High performance and low power multi-bi

    2024年02月02日
    瀏覽(29)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包