關(guān)于
Android使用FFmpeg(一)--編譯ffmpeg
Android使用FFmpeg(二)--Android Studio配置ffmpeg
Android使用FFmpeg(三)--ffmpeg實(shí)現(xiàn)視頻播放
Android使用FFmpeg(四)--ffmpeg實(shí)現(xiàn)音頻播放(使用AudioTrack進(jìn)行播放)
Android使用FFmpeg(五)--ffmpeg實(shí)現(xiàn)音頻播放(使用openSL ES進(jìn)行播放)
Android使用FFmpeg(六)--ffmpeg實(shí)現(xiàn)音視頻同步播放
Android使用FFmpeg(七)--ffmpeg實(shí)現(xiàn)暫停、快退快進(jìn)播放
準(zhǔn)備工作
jni調(diào)用Java方法
AudioTrack使用
關(guān)于音頻
正文

和視頻播放一樣我們依照流程圖來實(shí)現(xiàn),從這個流程圖可以看出,音頻播放和視頻播放的大概流程并沒有多大差別,只是細(xì)節(jié)之處需要處理。
依照流程來實(shí)現(xiàn)
1.注冊組件,打開音頻文件并獲取內(nèi)容,找到音頻流:
av_register_all();
AVFormatContext *pFormatCtx = avformat_alloc_context();
//open
if (avformat_open_input(&pFormatCtx, input, NULL, NULL) != 0) {
LOGE("%s","打開輸入視頻文件失敗");
return;
}
//獲取視頻信息
if(avformat_find_stream_info(pFormatCtx,NULL) < 0){
LOGE("%s","獲取視頻信息失敗");
return;
}
int audio_stream_idx=-1;
int i=0;
for (int i = 0; i < pFormatCtx->nb_streams; ++i) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
LOGE(" 找到音頻id %d", pFormatCtx->streams[i]->codec->codec_type);
audio_stream_idx=i;
break;
}
}
2.獲取解碼器,申請avframe和avpacket:
//獲取解碼器上下文
AVCodecContext *pCodecCtx=pFormatCtx->streams[audio_stream_idx]->codec;
//獲取解碼器
AVCodec *pCodex = avcodec_find_decoder(pCodecCtx->codec_id);
//打開解碼器
if (avcodec_open2(pCodecCtx, pCodex, NULL)<0) {
}
//申請avpakcet,裝解碼前的數(shù)據(jù)
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//申請avframe,裝解碼后的數(shù)據(jù)
AVFrame *frame = av_frame_alloc();
3.初始化SwrContext,進(jìn)行重采樣
//得到SwrContext ,進(jìn)行重采樣,具體參考http://blog.csdn.net/jammg/article/details/52688506
SwrContext *swrContext = swr_alloc();
//緩存區(qū)
uint8_t *out_buffer = (uint8_t *) av_malloc(44100 * 2);
//輸出的聲道布局(立體聲)
uint64_t out_ch_layout=AV_CH_LAYOUT_STEREO;
//輸出采樣位數(shù) 16位
enum AVSampleFormat out_formart=AV_SAMPLE_FMT_S16;
//輸出的采樣率必須與輸入相同
int out_sample_rate = pCodecCtx->sample_rate;
//swr_alloc_set_opts將PCM源文件的采樣格式轉(zhuǎn)換為自己希望的采樣格式
swr_alloc_set_opts(swrContext, out_ch_layout, out_formart, out_sample_rate,
pCodecCtx->channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate, 0,
NULL);
swr_init(swrContext);
4.通過while循環(huán)讀取內(nèi)容,并通過AudioTrack進(jìn)行播放:
// 獲取通道數(shù) 2
int out_channer_nb = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
// 反射得到Class類型
jclass david_player = env->GetObjectClass(instance);
// 反射得到createAudio方法
jmethodID createAudio = env->GetMethodID(david_player, "createTrack", "(II)V");
// 反射調(diào)用createAudio
env->CallVoidMethod(instance, createAudio, 44100, out_channer_nb);
jmethodID audio_write = env->GetMethodID(david_player, "playTrack", "([BI)V");
int got_frame;
while (av_read_frame(pFormatCtx, packet) >= 0) {
if (packet->stream_index == audio_stream_idx) {
// 解碼 mp3 編碼格式frame----pcm frame
avcodec_decode_audio4(pCodecCtx, frame, &got_frame, packet);
if (got_frame) {
LOGE("解碼");
swr_convert(swrContext, &out_buffer, 44100 * 2, (const uint8_t **) frame->data, frame->nb_samples);
// 緩沖區(qū)的大小
int size = av_samples_get_buffer_size(NULL, out_channer_nb, frame->nb_samples,
AV_SAMPLE_FMT_S16, 1);
jbyteArray audio_sample_array = env->NewByteArray(size);
env->SetByteArrayRegion(audio_sample_array, 0, size, (const jbyte *) out_buffer);
env->CallVoidMethod(instance, audio_write, audio_sample_array, size);
env->DeleteLocalRef(audio_sample_array);
}
}
}
5.釋放需要釋放的資源:
av_frame_free(&frame);
swr_free(&swrContext);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
env->ReleaseStringUTFChars(input_, input);
java層創(chuàng)建AudioTrack方法。
jni通過調(diào)用java層的audiotrack方法來實(shí)現(xiàn)播放,但是java層的audiotrack方法又是通過調(diào)用底層的openesl es來進(jìn)行播放,相當(dāng)于繞了一個圈,所以在下篇文章中將實(shí)現(xiàn)使用opensl es來直接播放音頻。
public class MusicPlay {
static{
System.loadLibrary("avcodec-56");
System.loadLibrary("avdevice-56");
System.loadLibrary("avfilter-5");
System.loadLibrary("avformat-56");
System.loadLibrary("avutil-54");
System.loadLibrary("postproc-53");
System.loadLibrary("swresample-1");
System.loadLibrary("swscale-3");
System.loadLibrary("native-lib");
}
public native void playSound(String input);
private AudioTrack audioTrack;
// 這個方法 是C進(jìn)行調(diào)用
public void createTrack(int sampleRateInHz,int nb_channals) {
int channaleConfig;//通道數(shù)
if (nb_channals == 1) {
channaleConfig = AudioFormat.CHANNEL_OUT_MONO;
} else if (nb_channals == 2) {
channaleConfig = AudioFormat.CHANNEL_OUT_STEREO;
}else {
channaleConfig = AudioFormat.CHANNEL_OUT_MONO;
}
int buffersize=AudioTrack.getMinBufferSize(sampleRateInHz,
channaleConfig, AudioFormat.ENCODING_PCM_16BIT);
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,sampleRateInHz,channaleConfig,
AudioFormat.ENCODING_PCM_16BIT,buffersize,AudioTrack.MODE_STREAM);
audioTrack.play();
}
//C傳入音頻數(shù)據(jù)
public void playTrack(byte[] buffer, int lenth) {
if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
audioTrack.write(buffer, 0, lenth);
}
}
}
小結(jié)
按照流程,使用AudioTrack播放音頻就是這么回事。通過和上篇的播放視頻相比較,流程就差不多了:注冊->解封裝->獲取流->解碼->播放。
源碼地址