欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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;

    }
}