Android NDK开发之旅–NDK-FFmpeg音频解码与播放
程序员文章站
2023-11-30 14:44:52
android ndk开发之旅30–ndk-ffmpeg音频解码与播放
音频解码实现
音频解码也是直接使用ffmpeg的api来做。
public native void sound...
android ndk开发之旅30–ndk-ffmpeg音频解码与播放
音频解码实现
音频解码也是直接使用ffmpeg的api来做。
public native void sound(string input,string output);
其中,jni实现如下:
//重采样 #include "libswresample/swresample.h" #define max_audio_frme_size 48000 * 4 jniexport void jnicall java_com_nan_ffmpeg_utils_videoplayer_sound(jnienv *env, jobject instance, jstring input_, jstring output_) { const char *input = (*env)->getstringutfchars(env, input_, 0); const char *output = (*env)->getstringutfchars(env, output_, 0); //注册组件 av_register_all(); avformatcontext *pformatctx = avformat_alloc_context(); //打开音频文件 if (avformat_open_input(&pformatctx, input, null, null) != 0) { logi("%s", "无法打开音频文件"); return; } //获取输入文件信息 if (avformat_find_stream_info(pformatctx, null) < 0) { logi("%s", "无法获取输入文件信息"); return; } //获取音频流索引位置 int i = 0, audio_stream_idx = -1; for (; i < pformatctx->nb_streams; i++) { if (pformatctx->streams[i]->codec->codec_type == avmedia_type_audio) { audio_stream_idx = i; break; } } //获取解码器 avcodeccontext *codecctx = pformatctx->streams[audio_stream_idx]->codec; avcodec *codec = avcodec_find_decoder(codecctx->codec_id); if (codec == null) { logi("%s", "无法获取解码器"); return; } //打开解码器 if (avcodec_open2(codecctx, codec, null) < 0) { logi("%s", "无法打开解码器"); return; } //压缩数据 avpacket *packet = (avpacket *) av_malloc(sizeof(avpacket)); //解压缩数据 avframe *frame = av_frame_alloc(); //frame->16bit 44100 pcm 统一音频采样格式与采样率 swrcontext *swrctx = swr_alloc(); //重采样设置参数-------------start //输入的采样格式 enum avsampleformat in_sample_fmt = codecctx->sample_fmt; //输出采样格式16bit pcm enum avsampleformat out_sample_fmt = av_sample_fmt_s16; //输入采样率 int in_sample_rate = codecctx->sample_rate; //输出采样率 int out_sample_rate = 44100; //获取输入的声道布局 //根据声道个数获取默认的声道布局(2个声道,默认立体声stereo) //av_get_default_channel_layout(codecctx->channels); uint64_t in_ch_layout = codecctx->channel_layout; //输出的声道布局(立体声) uint64_t out_ch_layout = av_ch_layout_stereo; swr_alloc_set_opts(swrctx, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, 0, null); swr_init(swrctx); //输出的声道个数 int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout); //重采样设置参数-------------end //16bit 44100 pcm 数据 uint8_t *out_buffer = (uint8_t *) av_malloc(max_audio_frme_size); file *fp_pcm = fopen(output, "wb"); int got_frame = 0, index = 0, ret; //不断读取压缩数据 while (av_read_frame(pformatctx, packet) >= 0) { //解码 ret = avcodec_decode_audio4(codecctx, frame, &got_frame, packet); if (ret < 0) { logi("%s", "解码完成"); } //解码一帧成功 if (got_frame > 0) { logi("解码:%d", index++); swr_convert(swrctx, &out_buffer, max_audio_frme_size, frame->data, frame->nb_samples); //获取sample的size int out_buffer_size = av_samples_get_buffer_size(null, out_channel_nb, frame->nb_samples, out_sample_fmt, 1); fwrite(out_buffer, 1, out_buffer_size, fp_pcm); } av_free_packet(packet); } fclose(fp_pcm); av_frame_free(&frame); av_free(out_buffer); swr_free(&swrctx); avcodec_close(codecctx); avformat_close_input(&pformatctx); (*env)->releasestringutfchars(env, input_, input); (*env)->releasestringutfchars(env, output_, output); }
最终会输出pcm格式的文件。
音频播放
pcm音频播放的常用方法:
opensl rs audio track(webrtc就用到)下面我们通过audio track来进行播放。
主要的实现思路就是:
在c层调用java层的createaudiotrack方法,创建audiotrack对象。 然后在c层调用audiotrack的pkay、write、stop等方法进行播放控制。例子:
package com.nan.ffmpeg.utils; public class videoplayer { static { system.loadlibrary("avutil-54"); system.loadlibrary("swresample-1"); system.loadlibrary("avcodec-56"); system.loadlibrary("avformat-56"); system.loadlibrary("swscale-3"); system.loadlibrary("postproc-53"); system.loadlibrary("avfilter-5"); system.loadlibrary("avdevice-56"); system.loadlibrary("ffmpeg-lib"); } public native void render(string input, surface surface); public native void play(string input); /** * 创建一个audiotrac对象,用于播放 * * @return */ public audiotrack createaudiotrack(int samplerateinhz, int nb_channels) { //固定格式的音频码流 int audioformat = audioformat.encoding_pcm_16bit; log.i("jason", "nb_channels:" + nb_channels); //声道布局 int channelconfig; if (nb_channels == 1) { channelconfig = android.media.audioformat.channel_out_mono; } else if (nb_channels == 2) { channelconfig = android.media.audioformat.channel_out_stereo; } else { channelconfig = android.media.audioformat.channel_out_stereo; } int buffersizeinbytes = audiotrack.getminbuffersize(samplerateinhz, channelconfig, audioformat); audiotrack audiotrack = new audiotrack( audiomanager.stream_music, samplerateinhz, channelconfig, audioformat, buffersizeinbytes, audiotrack.mode_stream); //播放 //audiotrack.play(); //写入pcm //audiotrack.write(audiodata, offsetinbytes, sizeinbytes); //播放完调用stop即可 audiotrack.stop(); return audiotrack; } }
jni实现如下:
//音频播放 jniexport void jnicall java_com_nan_ffmpeg_utils_videoplayer_play(jnienv *env, jobject instance, jstring input_) { const char *input = (*env)->getstringutfchars(env, input_, 0); logi("%s", "sound"); //注册组件 av_register_all(); avformatcontext *pformatctx = avformat_alloc_context(); //打开音频文件 if (avformat_open_input(&pformatctx, input, null, null) != 0) { logi("%s", "无法打开音频文件"); return; } //获取输入文件信息 if (avformat_find_stream_info(pformatctx, null) < 0) { logi("%s", "无法获取输入文件信息"); return; } //获取音频流索引位置 int i = 0, audio_stream_idx = -1; for (; i < pformatctx->nb_streams; i++) { if (pformatctx->streams[i]->codec->codec_type == avmedia_type_audio) { audio_stream_idx = i; break; } } //获取解码器 avcodeccontext *codecctx = pformatctx->streams[audio_stream_idx]->codec; avcodec *codec = avcodec_find_decoder(codecctx->codec_id); if (codec == null) { logi("%s", "无法获取解码器"); return; } //打开解码器 if (avcodec_open2(codecctx, codec, null) < 0) { logi("%s", "无法打开解码器"); return; } //压缩数据 avpacket *packet = (avpacket *) av_malloc(sizeof(avpacket)); //解压缩数据 avframe *frame = av_frame_alloc(); //frame->16bit 44100 pcm 统一音频采样格式与采样率 swrcontext *swrctx = swr_alloc(); //重采样设置参数-------------start //输入的采样格式 enum avsampleformat in_sample_fmt = codecctx->sample_fmt; //输出采样格式16bit pcm enum avsampleformat out_sample_fmt = av_sample_fmt_s16; //输入采样率 int in_sample_rate = codecctx->sample_rate; //输出采样率 int out_sample_rate = in_sample_rate; //获取输入的声道布局 //根据声道个数获取默认的声道布局(2个声道,默认立体声stereo) //av_get_default_channel_layout(codecctx->channels); uint64_t in_ch_layout = codecctx->channel_layout; //输出的声道布局(立体声) uint64_t out_ch_layout = av_ch_layout_stereo; swr_alloc_set_opts(swrctx, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, 0, null); swr_init(swrctx); //输出的声道个数 int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout); //重采样设置参数-------------end //jni begin------------------ //jasonplayer jclass player_class = (*env)->getobjectclass(env, instance); //audiotrack对象 jmethodid create_audio_track_mid = (*env)->getmethodid(env, player_class, "createaudiotrack", "(ii)landroid/media/audiotrack;"); jobject audio_track = (*env)->callobjectmethod(env, instance, create_audio_track_mid, out_sample_rate, out_channel_nb); //调用audiotrack.play方法 jclass audio_track_class = (*env)->getobjectclass(env, audio_track); jmethodid audio_track_play_mid = (*env)->getmethodid(env, audio_track_class, "play", "()v"); jmethodid audio_track_stop_mid = (*env)->getmethodid(env, audio_track_class, "stop", "()v"); (*env)->callvoidmethod(env, audio_track, audio_track_play_mid); //audiotrack.write jmethodid audio_track_write_mid = (*env)->getmethodid(env, audio_track_class, "write", "([bii)i"); //jni end------------------ //16bit 44100 pcm 数据 uint8_t *out_buffer = (uint8_t *) av_malloc(max_audio_frme_size); int got_frame = 0, index = 0, ret; //不断读取压缩数据 while (av_read_frame(pformatctx, packet) >= 0) { //解码音频类型的packet if (packet->stream_index == audio_stream_idx) { //解码 ret = avcodec_decode_audio4(codecctx, frame, &got_frame, packet); if (ret < 0) { logi("%s", "解码完成"); } //解码一帧成功 if (got_frame > 0) { logi("解码:%d", index++); swr_convert(swrctx, &out_buffer, max_audio_frme_size, (const uint8_t **) frame->data, frame->nb_samples); //获取sample的size int out_buffer_size = av_samples_get_buffer_size(null, out_channel_nb, frame->nb_samples, out_sample_fmt, 1); //out_buffer缓冲区数据,转成byte数组 jbytearray audio_sample_array = (*env)->newbytearray(env, out_buffer_size); jbyte *sample_bytep = (*env)->getbytearrayelements(env, audio_sample_array, null); //out_buffer的数据复制到sampe_bytep memcpy(sample_bytep, out_buffer, out_buffer_size); //同步 (*env)->releasebytearrayelements(env, audio_sample_array, sample_bytep, 0); //audiotrack.write pcm数据 (*env)->callintmethod(env, audio_track, audio_track_write_mid, audio_sample_array, 0, out_buffer_size); //释放局部引用 (*env)->deletelocalref(env, audio_sample_array); } } av_free_packet(packet); } (*env)->callvoidmethod(env, audio_track, audio_track_stop_mid); av_frame_free(&frame); av_free(out_buffer); swr_free(&swrctx); avcodec_close(codecctx); avformat_close_input(&pformatctx); (*env)->releasestringutfchars(env, input_, input); }
测试:
@override public void onclick(view v) { string inputvideo = environment.getexternalstoragedirectory().getabsolutepath() + file.separatorchar + “input.mp4”; string inputaudio = environment.getexternalstoragedirectory().getabsolutepath() + file.separatorchar + “love.mp3”; switch (v.getid()) { case r.id.btn_play_audio: mplayer.play(inputvideo); break; } }