【音視頻進階開發指南筆記-3】 使用ffmpeg庫解碼mp3文件並用audiotrack來播放音樂


AudioTrackTest-ffmpeg

源碼分析

大致的流程

該app一共有三個線程,一個主線程,一個NativeMp3Player線程,一個accompany_decoder線程。播放音樂的時候:

(1)NativeMp3Player線程從packet_pool中讀audiopacket,然后將audiopacket中的buffer數據寫到audiotrack中;

(2)而accompany_decoder線程先調用decodePacket對packet進行解碼,得到pcm數據,然后打包成audiopacket,然后將audiopacket鏈接到packet_pool中。

1. NativeMp3Player

1. setDataSource進行初始化操作

    public int setDataSource(String path) {
        mDecoder = new MusicDecoder();
        // accompany_decoder中的初始化操作,就是ffmpeg的初始化操作
        return mDecoder.init(path);
    }

2. prepare創建audiotrack和線程

    public void prepare() {
        initPlayState();
        initAudioTrack();
        startPlayerThread();
    }
	private void initPlayState() {
        // 初始化播放的狀態
        isPlaying = false;
        isStop = false;
    }

    private void startPlayerThread() {
        // 創建線程
        mPlayerThread = new Thread(new PlayerThread(), "NativeMp3PlayerThread");
        // 開始執行線程
        mPlayerThread.start();
    }

    private void initAudioTrack() {
        // 獲得最小的buffer size
        int buffersize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
        // 創建audiotrack
        mAudioTrack = new AudioTrack.Builder()
                .setAudioAttributes(new AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_MEDIA)
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                        .build())
                .setAudioFormat(new AudioFormat.Builder()
                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                        .setSampleRate(44100)
                        .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
                        .build())
                .setBufferSizeInBytes(buffersize)
                .build();
    }

    class PlayerThread implements Runnable {
        private short[] samples;

        @Override
        public void run() {
            int sample_count = 0;
            boolean isPlayTemp = isPlaying = false;
            try {
                // 按最大的192000來創建一個buffer,獲得audiopacket數據
                samples = new short[DECODE_BUFFER_SIZE];
                int[] extraSlientSampleSize = new int[1];

                while (!isStop) {
                    extraSlientSampleSize[0] = 0;
                    // 讀取pcm數據到buffer中
                    sample_count = mDecoder.readSamples(samples, extraSlientSampleSize);

                    if (sample_count == -2) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        continue;
                    }
                    if (sample_count < 0) {
                        break;
                    }

                    if (null != mAudioTrack && mAudioTrack.getState() != AudioTrack.STATE_UNINITIALIZED) {
                        // 寫到audiotrack中
                        mAudioTrack.write(samples, 0, sample_count);
                    }

                    while (true) {
                        synchronized (NativeMp3Player.class) {
                            isPlayTemp = isPlaying;
                        }
                        if (isPlayTemp)
                            break;
                        else
                            Thread.yield();
                    }
                }
                mDecoder.destory();
            } catch (Error e) {
                e.printStackTrace();
            }
            samples = null;
        }
    }

3. start開始播放

    public void start() {
        synchronized (NativeMp3Player.class) {
            try {
                if (null != mAudioTrack) {
                    // 開始播放
                    mAudioTrack.play();
                }
            } catch (Throwable t) {

            }
            isPlaying = true;
        }
    }

4. stop停止播放

    public void stop() {
        if (!isStop && null != mAudioTrack) {
            if (null != mAudioTrack && mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
                try {
                    // stop停止播放
                    mAudioTrack.stop();
                } catch (Throwable t) {
                    t.printStackTrace();
                }
            }
            // 設置狀態
            isPlaying = true;
            isStop = true;
            try {
                Log.i(TAG, "join decodeMp3Thread...");
                if (null != mPlayerThread) {
                    // 銷毀線程
                    mPlayerThread.join();
                    mPlayerThread = null;
                }
                Log.e(TAG, "decodeMp3Thread ended....");
            } catch (Throwable t) {
                t.printStackTrace();
            }
            closeAudioTrack();
            destroy();
        }
    }

2. AccompanyDecoderController

1. init初始化ffmpeg,創建線程

int AccompanyDecoderController::init(const char *acPath) {
    LOGI("AccompanyDecoderController::Init");
    macDecoder = new AccompanyDecoder();
    // 1. 進行ffmpeg的初始化操作
    if (macDecoder->init(acPath) < 0) {
        LOGI("macDecoder->init fail...");
        return -1;
    } else {
        LOGI("macDecoder->init success...");
    }

    mpacketPool = PacketPool::GetInstance();
    // 2. 初始化audiopacket隊列
    mpacketPool->initDecoderAccompanyPacketQueue();
    // 3. 初始化解碼線程,該線程用來填充數據的
    initDecoderThread();
    return 0;
}
1. macDecoder->init(acPath)
int AccompanyDecoder::init(const char *fileString) {
    LOGI("enter AccompanyDecoder::init");
    mavFormatContext = NULL;
    mstream_index = -1;
    mavCodecContext = NULL;
    mswrContext = NULL;
    AVCodec * avCodec = NULL;
    mindex = 0;

    mpAudioFrame = av_frame_alloc();
    if (mpAudioFrame == NULL) {
        LOGI("av_frame_alloc mpAudioFrame fail...");
        return -1;
    } else {
        LOGI("av_frame_alloc mpAudioFrame success...");
    }
    maudioBuffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE*2);
    if (maudioBuffer == NULL) {
        LOGI("av_malloc audiobuffer fail...");
        return -1;
    } else {
        LOGI("av_malloc audiobuffer success...");
    }
    // 注冊解碼器
    avcodec_register_all();
    av_register_all();
    mavFormatContext = avformat_alloc_context();
    LOGI("open ac file %s...", fileString);

    // 打開文件,並解析文件,然后填充avformat
    int result = avformat_open_input(&mavFormatContext, fileString, NULL, NULL);
    if (result != 0) {
        LOGI("can't open file %s result %s", fileString, av_err2str(result));
        return -1;
    } else {
        LOGI("open file %s success and result is %s", fileString, av_err2str(result));
    }

    // 檢查文件中的流信息
    result = avformat_find_stream_info(mavFormatContext, NULL);
    if (result < 0) {
        LOGI("fail avformat avformat_find_stream_info %s result %s", fileString,
             av_err2str(result));
        return -1;
    } else {
        LOGI("avformat_find_stream_info success result is %s", fileString, av_err2str(result));
    }
    // 找stream
    mstream_index = av_find_best_stream(mavFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    LOGI("stream_index is %d", mstream_index);
    if (mstream_index == -1) {
        LOGI("no audio stream");
        return -1;
    }
    mavCodecContext = mavFormatContext->streams[mstream_index]->codec;
    LOGI("avCodecContext->codec_id is %d AV_CODEC_ID_AAC is %d", mavCodecContext->codec_id,
         AV_CODEC_ID_AAC);
    // 找解碼器
    avCodec = avcodec_find_decoder(mavCodecContext->codec_id);
    if (avCodec == NULL) {
        LOGI("Unsupported codec");
        return -1;
    }
    // 打開解碼器
    result = avcodec_open2(mavCodecContext, avCodec, NULL);
    if (result < 0) {
        LOGI("avcodec_open2 fail avformat_find_stream_info result is %s", av_err2str(result));
        return -1;
    } else {
        LOGI("avcodec_open2 sucess avformat_find_stream_info result is %s", av_err2str(result));
    }
    if (!(mavCodecContext->sample_fmt == AV_SAMPLE_FMT_S16)) {
        LOGI("because of audio Codec Is Not Supported so we will init swresampler...");
        mswrContext = swr_alloc();
        // 采樣率轉換的設置
        mswrContext = swr_alloc_set_opts(mswrContext,
                                         av_get_default_channel_layout(OUT_PUT_CHANNELS),
                                         AV_SAMPLE_FMT_S16, 44100,
                                         av_get_default_channel_layout(mavCodecContext->channels),
                                         mavCodecContext->sample_fmt, mavCodecContext->sample_rate,
                                         0, NULL);
        if (!mswrContext || swr_init(mswrContext)) {
            if (mswrContext)
                swr_free(&mswrContext);
            avcodec_close(mavCodecContext);
            LOGI("init resampler failed...");
            return -1;
        }
    }
    LOGI(" channels is %d sampleRate is %d", mavCodecContext->channels,
         mavCodecContext->sample_rate);

    return 0;
}
2. mpacketPool->initDecoderAccompanyPacketQueue
void PacketPool::initDecoderAccompanyPacketQueue() {
    const char* name = "decoder accompany packet queue";
    // packetpool就是一個隊列
    macPacketQueue = new PacketQueue(name);
}

PacketQueue::PacketQueue() {
    init();
}

void PacketQueue::init() {
    LOGI("enter PacketQueue init");
    // 隊列需要鎖來保護
    int initLockCode = pthread_mutex_init(&mLock, NULL);
    // 實現block機制,隊列空了,就等待
    int initConditionCode = pthread_cond_init(&mCondition, NULL);
    // 隊列元素的個數
    mNbPackets = 0;
    // 隊列頭指針
    mFirst = NULL;
    // 隊列尾指針
    mLast = NULL;
    mAbortRequest = false;
}

3. initDecoderThread(),啟動accompany_decoder線程,進行解碼,然后push audiopacket到隊列中
void AccompanyDecoderController::initDecoderThread() {
    LOGI("enter AccompanyDecoderController::initDecoderThread");
    // 設置狀態
    isRunning = true;
    // 初始化鎖和條件變量
    pthread_mutex_init(&mLock, NULL);
    pthread_cond_init(&mCondition, NULL);
    // 創建線程
    pthread_create(&songDecoderThread, NULL, startDecoderThread, this);
}

void* AccompanyDecoderController::startDecoderThread(void* ptr) {
    LOGI("enter AccompanyDecoderController::startDecoderThread");
    AccompanyDecoderController* decoderController = (AccompanyDecoderController *) ptr;
    int getLockCode = pthread_mutex_lock(&decoderController->mLock);
    while (decoderController->isRunning) {
        // 解碼,獲取包,鏈接到packet_pool中
        decoderController->decodeSongPacket();
        if (decoderController->mpacketPool->getDecoderAccompanyPacketQueueSize() > QUEUE_SIZE_MAX_THRESHOLD) {
            pthread_cond_wait(&decoderController->mCondition, &decoderController->mLock);
        }
    }
}

void AccompanyDecoderController::decodeSongPacket() {
    LOGI("AccompanyDecoderController::decodeSongPacket");
    // 1. 解碼,獲取audiopacket
    AudioPacket* acPacket = macDecoder->decodePacket();
    if (acPacket != NULL) {
        acPacket->maction = AudioPacket::AUDIO_PACKET_ACTION_PLAY;
        // 2. 鏈接到packet_pool中
        mpacketPool->pushDecoderAccompanyPacketQueue(acPacket);
    }
}
1. macDecoder->decodePacket()
AudioPacket* AccompanyDecoder::decodePacket() {
    int ret = 1;
    int gotframe = 0;
    AVSampleFormat outSampleFmt = AV_SAMPLE_FMT_S16;
    AudioPacket* samplePacket = NULL;

    av_init_packet(&mpacket);

    // 從文件中讀取packet
    while (av_read_frame(mavFormatContext, &mpacket) >= 0) {
        //LOGI(" av_read_frame start");
        if (mpacket.stream_index == mstream_index) {
            //LOGI("av_read_frame mstream_index");
            // 從packet中解碼到frame中
            if (avcodec_decode_audio4(mavCodecContext, mpAudioFrame, &gotframe, &mpacket) < 0) {
                LOGI("decode audio error, skip packet");
                return NULL;
            }
            //LOGI("av_read_frame avcodec_decode_audio4");
            if (gotframe) {
                //LOGI("av_read_frame gotframe");
                int outBufferSize=mpAudioFrame->nb_samples * OUT_PUT_CHANNELS;
                int numFrames = 0;
                // 重新采樣
                if (mswrContext) {
                    // 進行重采樣操作
                    numFrames = swr_convert(mswrContext, &maudioBuffer,
                                            mpAudioFrame->nb_samples,
                            (const uint8_t **)mpAudioFrame->data,
                            mpAudioFrame->nb_samples);
                    if (numFrames < 0) {
                        LOGI("fail resample audio");
                        return NULL;
                    }
                    LOGI("mindex:%5d\t pts:%lld\t packet size:%d out_buffer_size: %d\n",mindex,mpacket.pts,mpacket.size, outBufferSize);
                    // copy to AudioPacket
                    short * samples = new short[outBufferSize];
                    memcpy(samples, maudioBuffer, outBufferSize*2);
                    // 生成audiopacket,並返回
                    samplePacket = new AudioPacket();
                    if (samplePacket == NULL) {
                        return NULL;
                    }
                    samplePacket->mbuffer = samples;
                    samplePacket->msize = outBufferSize;
                    mindex++;
                    break;
                }
            }
        }
    }
    //LOGI(" end");
    av_packet_unref(&mpacket);

    return samplePacket;
}

2. mpacketPool->pushDecoderAccompanyPacketQueue(acPacket)
void PacketPool::pushDecoderAccompanyPacketQueue(AudioPacket *audioPacket) {
    macPacketQueue->put(audioPacket);
}

int PacketQueue::put(AudioPacket *audioPacket) {
    LOGI("enter PacketQueue put...");
    if (mAbortRequest) {
        delete audioPacket;
        return -1;
    }
    // 創建一個節點
    AudioPacketList *pkt1 = new AudioPacketList();
    if (!pkt1)
        return -1;
    pkt1->pkt = audioPacket;
    pkt1->next = NULL;

    // 對隊列進行操作時,先獲取鎖
    int getLockCode = pthread_mutex_lock(&mLock);
    if (mLast == NULL) {
        // 如果隊列為空
        mFirst = pkt1;
    } else {
        // 插入尾部
        mLast->next = pkt1;
    }

    mLast = pkt1;
    mNbPackets++;

    // 發送條件變量
    pthread_cond_signal(&mCondition);
    pthread_mutex_unlock(&mLock);
    return 0;
}

2. readSamples,讀線程,NativeMp3Player線程會來讀取數據

int AccompanyDecoderController::readSamples(short* samples, int size, int* slientSizeArr) {
    LOGI("AccompanyDecoderController::readSamples");
    int result = -1;
    AudioPacket* acPacket = NULL;
    // 1. 從隊列中獲取audiopacket包
    mpacketPool->getDecoderAccompanyPacket(&acPacket, true);
    if (NULL != acPacket) {
        int samplePacketSize = acPacket->msize;
        if (samplePacketSize != -1 && samplePacketSize <= size) {
            // 2. 將audiopacket中的數據拷貝到buffer中
            memcpy(samples, acPacket->mbuffer, samplePacketSize * 2);
            delete acPacket;
            result = samplePacketSize;
        }
    } else {
        result = -2;
    }
    // 3. 當隊列中的包少於20個時,通知寫線程,進行寫操作
    if (mpacketPool->getDecoderAccompanyPacketQueueSize() < QUEUE_SIZE_MIN_THRESHOLD) {
        int getLockCode = pthread_mutex_lock(&mLock);
        if (result != -1) {
            pthread_cond_signal(&mCondition);
        }
        pthread_mutex_unlock(&mLock);
    }
    return result;
}

1. mpacketPool->getDecoderAccompanyPacket(&acPacket, true)

int PacketPool::getDecoderAccompanyPacket(AudioPacket **audioPacket, bool block) {
    int result = -1;
    if (NULL != macPacketQueue) {
        result = macPacketQueue->get(audioPacket, block);
    }
    return result;
}

int PacketQueue::get(AudioPacket **audioPacket, bool block) {
    LOGI("enter PacketQueue get...");
    AudioPacketList* pkt1;
    int ret;

    // 先獲取鎖
    int getLockCode = pthread_mutex_lock(&mLock);
    for (;;) {
        if (mAbortRequest) {
            ret = -1;
            break;
        }
        // 獲取頭部
        pkt1 = mFirst;
        if (pkt1) {
            // 指導下一個元素
            mFirst = pkt1->next;
            if (!mFirst)
                mLast = NULL;
            mNbPackets--;
            *audioPacket = pkt1->pkt;
            delete pkt1;
            pkt1 = NULL;
            ret = 1;
            break;
        } else if (!block) {
            ret = 0;
            break;
        } else {
            // 如果為空,則block着
            pthread_cond_wait(&mCondition, &mLock);
        }
    }

    pthread_mutex_unlock(&mLock);
    return ret;
}

3. destroy,停止的時候,進行清除工作

void AccompanyDecoderController::destroy() {
    LOGI("AccompanyDecoderController::Destroy");
    destroyDecoderThread();
    mpacketPool->abortDecoderAccompanyPacketQueue();
    mpacketPool->destoryDecoderAccompanyPacketQueue();
    if (NULL != macDecoder) {
        macDecoder->destroy();
        delete macDecoder;
        macDecoder = NULL;
    }
}

實用技巧

1. JNI中數組和字符串的使用方式

        jshort* target = env->GetShortArrayElements(array, 0);
        jint* slientSizeArr = env->GetIntArrayElements(extraSlientSampleSize, 0);
        int result = mDecoderController->readSamples(target, size, slientSizeArr);
        env->ReleaseIntArrayElements(extraSlientSampleSize, slientSizeArr, 0);
        env->ReleaseShortArrayElements(array, target, 0);
        
        const char * acFilePath = env->GetStringUTFChars(acFilePathParam, NULL);
        mDecoderController->init(acFilePath);
        env->ReleaseStringUTFChars(acFilePathParam, acFilePath);

2. java中線程的使用方式

		// 創建線程,線程名
		mPlayerThread = new Thread(new PlayerThread(), "NativeMp3PlayerThread");
		// 然后調用start函數
        mPlayerThread.start();

// 繼承Runnable
class PlayerThread implements Runnable {
        @Override
        public void run() {}
}

3. c++中線程的使用方式

// 創建pthread_t變量
pthread_t songDecoderThread;
// 執行startDecoderThread函數,傳遞this參數
pthread_create(&songDecoderThread, NULL, startDecoderThread, this);
// 銷毀線程
pthread_join(songDecoderThread, &status);

4. lock和condition的使用方式

    // 首先定義變量
	pthread_mutex_t mLock;
    pthread_cond_t mCondition;
	// 然后進行初始化
    pthread_mutex_init(&mLock, NULL);
    pthread_cond_init(&mCondition, NULL);
	// 使用,lock和unlock
	pthread_mutex_lock(&mLock);
	pthread_cond_signal(&mCondition);
    pthread_mutex_unlock(&mLock);
	// 最后進行銷毀操作
	pthread_mutex_destroy(&mLock);
    pthread_cond_destroy(&mCondition);

5. AudioTrack的使用流程

// 先獲取最小buffer
// 然后創建audiotrack
int buffersize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
        mAudioTrack = new AudioTrack.Builder()
                .setAudioAttributes(new AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_MEDIA)
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                        .build())
                .setAudioFormat(new AudioFormat.Builder()
                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                        .setSampleRate(44100)
                        .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
                        .build())
                .setBufferSizeInBytes(buffersize)
                .build();
// write是一個循環操作
// 寫數據
mAudioTrack.write(samples, 0, sample_count);
// 播放只需要調用一次
mAudioTrack.play();

mAudioTrack.stop();
mAudioTrack.release();

6. AudioTrack的內部實現流程(后面再結合看)

問題

1. audiotrack的play在write之前,可能會出錯,造成無法播放的現象

write和play之間存在一個時差問題,把write放在onCreate的時候,然后按下按鈕的時候,就play。

參考

1. 源碼位置:https://github.com/mashenlyl/AudioTrackTest


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM