上篇说了,画面输出,这一篇轮到视频的另外主体,音频。
由于Native层没有好办法可以去调用到音频输出设备,所以需要把输出放到Java层,这里初始化输出就会比画面输出麻烦点,要从C调用到Java层去启动音频设备
/**
* 创建 AudioTrack
* 由 C 反射调用
*
* @param sampleRate 采样率
* @param channels 通道数
*/
public void createAudioTrack(int sampleRate, int channels) {
int channelConfig;
if (channels == 1) {
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
} else if (channels == 2) {
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
} else {
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
}
int bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig,
AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM);
audioTrack.play();
}
/**
* 播放 AudioTrack
* 由 C 反射调用
*
* @param data
* @param length
*/
public void playAudioTrack(byte[] data, int length) {
if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
audioTrack.write(data, 0, length);
}
}
/**
* 释放 AudioTrack
* 由 C 反射调用
*/
public void releaseAudioTrack() {
if (audioTrack != null) {
if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
audioTrack.stop();
}
audioTrack.release();
audioTrack = null;
}
}
这几个就是用于C层反射调用启动音频输出的方法。后面会用到。
接着我们要准备音频输出的一些参数:
AVCodecContext *codec_context = player->audio_codec_context;
player->swr_context = swr_alloc();
player->audio_out_buffer = (uint8_t *) av_malloc(44100 * 2);
uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO;
enum AVSampleFormat out_format = AV_SAMPLE_FMT_S16;
int out_sample_rate = player->audio_codec_context->sample_rate;
swr_alloc_set_opts(player->swr_context,
out_channel_layout, out_format, out_sample_rate,
codec_context->channel_layout, codec_context->sample_fmt, codec_context->sample_rate,
0, NULL);
swr_init(player->swr_context);
player->out_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
熟悉音频的各位应该知道音频输出需要的几个参数:声道(channel_layout)、采样率(sample_rate),格式(format),这几个必须设置,否则是会输出失败的。
接着就是去调用Java,创建并播放音频了。
jclass player_class = env->GetObjectClass(player->instance);
jmethodID create_audio_track_method_id = env->GetMethodID(player_class, "createAudioTrack", "(II)V");
env->CallVoidMethod(player->instance, create_audio_track_method_id, 44100, player->out_channels);
player->play_audio_track_method_id = env->GetMethodID(player_class, "playAudioTrack", "([BI)V");
接下来就是输出到音频设备上。
因为前期已经设置了输出参数,而且音频是流,不是画面那样一帧一输出,是持续输出的,所以输出就不用在设置什么了,循环输出到设备就行了。
swr_convert(player->swr_context, &(player->audio_out_buffer), 44100 * 2, (const uint8_t **) frame->data,
frame->nb_samples);
int size = av_samples_get_buffer_size(NULL, player->out_channels, frame->nb_samples, AV_SAMPLE_FMT_S16, 1);
jbyteArray audio_sample_array = env->NewByteArray(size);
env->SetByteArrayRegion(audio_sample_array, 0, size, (const jbyte *) player->audio_out_buffer);
env->CallVoidMethod(player->instance, player->play_audio_track_method_id, audio_sample_array, size);
env->DeleteLocalRef(audio_sample_array);
这样音频输出也就完成了。
后面来说说,画面和音频同时输出,多线程处理视频画面和音频。