一、Android AudioTrack簡(jiǎn)介
在Android中播放音頻可以用MediaPlayer和AudioTrack兩種方案的谜慌,但是兩種方案是有很大區(qū)別的然想,MediaPlayer可以播放多種格式的聲音文件,例如MP3欣范,AAC变泄,WAV,OGG恼琼,MIDI等妨蛹。而AudioTrack只能播放PCM數(shù)據(jù)流。
事實(shí)上晴竞,兩種本質(zhì)上是沒啥區(qū)別的蛙卤,MediaPlayer在播放音頻時(shí),在framework層還是會(huì)創(chuàng)建AudioTrack噩死,把解碼后的PCM數(shù)流傳遞給AudioTrack表窘,最后由AudioFlinger進(jìn)行混音典予,傳遞音頻給硬件播放出來。利用AudioTrack播放只是跳過Mediaplayer的解碼部分而已乐严。Mediaplayer的解碼核心部分是基于OpenCORE 來實(shí)現(xiàn)的瘤袖,支持通用的音視頻和圖像格式,codec使用的是OpenMAX接口來進(jìn)行擴(kuò)展昂验。因此使用audiotrack播放mp3文件的話捂敌,要自己加入一個(gè)音頻解碼器,如libmad既琴。否則只能播放PCM數(shù)據(jù)占婉,如大多數(shù)WAV格式的音頻文件。
如果是實(shí)時(shí)的音頻數(shù)據(jù)甫恩,那么只能用AudioTrack進(jìn)行播放逆济。
音頻所占用字節(jié)數(shù) = 通道數(shù) * 采用頻率(Hz) * 采用位數(shù)(byte)
二、具體實(shí)現(xiàn)
native-lib.cpp
2.1音頻轉(zhuǎn)PCM和使用native調(diào)用AudioTrack播放音頻文件磺箕,代碼公共部分
#include <jni.h>
#include <string>
#include <android/log.h>
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
#include <android/native_window_jni.h>
#include <unistd.h>
};
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"MusicPlayer",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"MusicPlayer",FORMAT,##__VA_ARGS__);
2.2 音頻轉(zhuǎn)PCM
extern "C"
JNIEXPORT void JNICALL
Java_com_fmtech_ffmpegmusic_MusicPlayer_audioToPcm(JNIEnv *env, jobject instance, jstring inputPath_, jstring outputPath_) {
const char *inputPath = env->GetStringUTFChars(inputPath_, 0);
const char *outputPath = env->GetStringUTFChars(outputPath_, 0);
av_register_all();
AVFormatContext *pFormatCtx = avformat_alloc_context();
if(avformat_open_input(&pFormatCtx, inputPath, NULL, NULL) != 0){
LOGE("Open input failed.");
return;
}
if(avformat_find_stream_info(pFormatCtx, NULL) < 0){
LOGE("Find stream info failed.");
return;
}
int audio_stream_idx = -1;
int i = 0;
for(i = 0; i < pFormatCtx->nb_streams; i++){
if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
audio_stream_idx = i;
break;
}
}
AVCodecContext *pCodecCtx = pFormatCtx->streams[audio_stream_idx]->codec;
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0){
LOGE("avcodec_open2 failed.");
return;
}
AVPacket *packet = (AVPacket*)av_malloc(sizeof(AVPacket));
AVFrame *frame = av_frame_alloc();
SwrContext *swrContext = swr_alloc();
int got_frame;
uint8_t *out_buffer = (uint8_t*)av_malloc(44100 * 2);
uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
//輸出采樣位數(shù)
enum AVSampleFormat out_sample_format = AV_SAMPLE_FMT_S16;
//輸出采樣率必須與輸入相同
int out_sample_rate = pCodecCtx->sample_rate;
swr_alloc_set_opts(swrContext, out_ch_layout, out_sample_format, out_sample_rate,
pCodecCtx->channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate, 0, NULL);
swr_init(swrContext);
int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);
LOGI("-------Out channecl nb:%d",out_channel_nb);
FILE *pcm_file = fopen(outputPath, "wb");
while(av_read_frame(pFormatCtx, packet) >= 0){
if(packet->stream_index == audio_stream_idx){
avcodec_decode_audio4(pCodecCtx, frame, &got_frame, packet);
if(got_frame){
swr_convert(swrContext, &out_buffer, 44100 * 2, (const uint8_t **) frame->data, frame->nb_samples);
int out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb, frame->nb_samples, out_sample_format, 1);
fwrite(out_buffer, 1, out_buffer_size, pcm_file);
}
}
}
LOGI("-------Decode audio success.");
fclose(pcm_file);
av_frame_free(&frame);
av_free(out_buffer);
swr_free(&swrContext);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
env->ReleaseStringUTFChars(inputPath_, inputPath);
env->ReleaseStringUTFChars(outputPath_, outputPath);
}
2.3使用native調(diào)用AudioTrack播放音頻文件
extern "C"
JNIEXPORT void JNICALL
Java_com_fmtech_ffmpegmusic_MusicPlayer_playMusic(JNIEnv *env, jobject instance,
jstring inputPath_) {
const char *inputPath = env->GetStringUTFChars(inputPath_, 0);
av_register_all();
AVFormatContext *pFormatCtx = avformat_alloc_context();
if(avformat_open_input(&pFormatCtx, inputPath, NULL, NULL) != 0){
LOGE("Open input failed.");
return;
}
if(avformat_find_stream_info(pFormatCtx, NULL) < 0){
LOGE("Find stream info failed.");
return;
}
int audio_stream_idx = -1;
int i = 0;
for(i = 0; i < pFormatCtx->nb_streams; i++){
if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
audio_stream_idx = i;
break;
}
}
AVCodecContext *pCodecCtx = pFormatCtx->streams[audio_stream_idx]->codec;
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0){
LOGE("avcodec_open2 failed.");
return;
}
AVPacket *packet = (AVPacket*)av_malloc(sizeof(AVPacket));
AVFrame *frame = av_frame_alloc();
SwrContext *swrContext = swr_alloc();
int got_frame;
uint8_t *out_buffer = (uint8_t*)av_malloc(44100 * 2);
uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
//輸出采樣位數(shù)
enum AVSampleFormat out_sample_format = AV_SAMPLE_FMT_S16;
//輸出采樣率必須與輸入相同
int out_sample_rate = pCodecCtx->sample_rate;
swr_alloc_set_opts(swrContext, out_ch_layout, out_sample_format, out_sample_rate,
pCodecCtx->channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate, 0, NULL);
swr_init(swrContext);
int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);
LOGI("-------Out channecl nb:%d",out_channel_nb);
//調(diào)用MusicPlayer.java中的方法
jclass clazzMusicPlayer = env->GetObjectClass(instance);
jmethodID initAudioTrack = env->GetMethodID(clazzMusicPlayer, "initAudioTrack", "(II)V");
jmethodID playTrack = env->GetMethodID(clazzMusicPlayer, "playTrack", "([BI)V");
env->CallVoidMethod(instance, initAudioTrack, 44100, out_channel_nb);
int frameCount=0;
while(av_read_frame(pFormatCtx, packet) >= 0){
if(packet->stream_index == audio_stream_idx){
avcodec_decode_audio4(pCodecCtx, frame, &got_frame, packet);
if(got_frame){
LOGI("Decode %d frame.", frameCount++);
swr_convert(swrContext, &out_buffer, 44100 * 2, (const uint8_t **) frame->data, frame->nb_samples);
int out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb, frame->nb_samples, out_sample_format, 1);
jbyteArray audio_sample_array = env->NewByteArray(out_buffer_size);
env->SetByteArrayRegion(audio_sample_array, 0, out_buffer_size, (const jbyte *) out_buffer);
env->CallVoidMethod(instance, playTrack, audio_sample_array, out_buffer_size);
env->DeleteLocalRef(audio_sample_array);
}
}
}
LOGI("-------Play audio finish.");
av_frame_free(&frame);
av_free(out_buffer);
swr_free(&swrContext);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
env->ReleaseStringUTFChars(inputPath_, inputPath);
}
MusicPlayer.java
public class MusicPlayer {
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");
}
private AudioTrack mAudioTrack;
public void initAudioTrack(int sampleRateInHz, int nb_channels){
int channelConfig;
if(nb_channels == 1){
channelConfig = AudioFormat.CHANNEL_OUT_MONO;//單聲道
}else if(nb_channels == 2){
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;//雙聲道立體聲
}else{
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
}
////根據(jù)采樣率奖慌,采樣精度,單雙聲道來得到buffer的大小
int bufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
// AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)
// AudioFormat.ENCODING_PCM_16BIT 設(shè)置音頻數(shù)據(jù)塊是8位還是16位松靡,這里設(shè)置為16位简僧。
// AudioTrack.MODE_STREAM設(shè)置模式類型,在這里設(shè)置為流類型雕欺,第二種MODE_STATIC
mAudioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC, // 指定流的類型
sampleRateInHz,// 設(shè)置音頻數(shù)據(jù)的采樣率
channelConfig,
AudioFormat.ENCODING_PCM_16BIT,
bufferSize, AudioTrack.MODE_STREAM);
mAudioTrack.play();//very important 啟動(dòng)音頻設(shè)備
}
public synchronized void playTrack(byte[] buffer, int length){
if(null != mAudioTrack && mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING){
mAudioTrack.write(buffer, 0, length);
}
}
public native void audioToPcm(String inputPath, String outputPath);
public native void playMusic(String inputPath);
}