ffmpeg音頻轉原生的pcm及Native層回調播放

PCM(Pulse Code Modulation),脈衝編碼調製。人耳聽到的是模擬信號,PCM是把聲音從模擬信號轉化爲數字信號的技術。原理是用一個固定的頻率對模擬信號進行採樣,採樣後的信號在波形上看就像一串連續的幅值不一的脈衝(脈搏似的短暫起伏的電衝擊),把這些脈衝的幅值按一定精度進行量化,這些量化後的數值被連續的輸出、傳輸、處理或記錄到存儲介質中,所有這些組成了數字音頻的產生過程(抽樣、量化、編碼三個過程)。

播放音樂時,應用程序從存儲介質中讀取音頻數據(MP3、WMA、AAC…),經過解碼後,最終送到音頻驅動程序中的就是PCM數據,反過來,在錄音時,音頻驅動不停地把採樣所得的PCM數據送回給應用程序,由應用程序完成壓縮、存儲等任務。

下面我們通過ffmpeg音頻轉原生的PCM

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public void play(View view) {
        player();
    }
    private static String[] permissions = new String[]{
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE
    };
    private void player(){
        String[] permissions1 = checkPermission(this);
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
            if(permissions1.length<=0){
                MyPlayer myPlayer = new MyPlayer();
                String input = new File(Environment.getExternalStorageDirectory(),"input.mp3").getAbsolutePath();
                String output = new File(Environment.getExternalStorageDirectory(),"output.pcm").getAbsolutePath();
                myPlayer.sound(input,output);
            }else{
                ActivityCompat.requestPermissions(this, permissions, 100);
            }
        }else{//6.0以下不需要申請權限
            MyPlayer myPlayer = new MyPlayer();
            String input = new File(Environment.getExternalStorageDirectory(),"input.mp3").getAbsolutePath();
            String output = new File(Environment.getExternalStorageDirectory(),"output.pcm").getAbsolutePath();
            myPlayer.sound(input,output);
        }
    }
    public static String[] checkPermission(Context context){
        List<String> data = new ArrayList<>();//存儲未申請的權限
        for (String permission : permissions) {
            int checkSelfPermission = ContextCompat.checkSelfPermission(context, permission);
            if(checkSelfPermission == PackageManager.PERMISSION_DENIED){//未申請
                data.add(permission);
            }
        }
        return data.toArray(new String[data.size()]);
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(requestCode == 100){
            boolean flag = true;
            for(int i :  grantResults){
                if(i != PackageManager.PERMISSION_GRANTED){
                    flag = false;
                    break;
                }
            }
            if(flag){
                player();
            }else{
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            }
        }else{
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}
public class MyPlayer {
    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("native-lib");
    }
    public native void sound(String input,String output);
}

核心代碼

#include <jni.h>
#include <string>
#include <android/log.h>
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"twy",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"twy",FORMAT,##__VA_ARGS__);

#define MAX_AUDIO_FRME_SIZE 48000 * 4
extern  "C"{
//封裝格式
#include "libavformat/avformat.h"
//解碼
#include "libavcodec/avcodec.h"
//縮放
#include "libswscale/swscale.h"
//重採樣
#include "libswresample/swresample.h"
};
extern "C"
{
    JNIEXPORT void JNICALL
    Java_com_dongnao_ffmpegmusicdemo_MyPlayer_sound(JNIEnv *env, jobject instance, jstring input_,
                                                       jstring output_) {
        const char *input = env->GetStringUTFChars(input_, 0);
        const char *output = env->GetStringUTFChars(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(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 *swrContext = swr_alloc();
    //    音頻格式  重採樣設置參數
        AVSampleFormat in_sample = codecCtx->sample_fmt;
    //    輸出採樣格式
        AVSampleFormat out_sample=AV_SAMPLE_FMT_S16;
    // 輸入採樣率
        int in_sample_rate = codecCtx->sample_rate;
    //    輸出採樣
        int out_sample_rate=44100;
    //    輸入聲道佈局
        uint64_t in_ch_layout=codecCtx->channel_layout;
    //    輸出聲道佈局
        uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
        swr_alloc_set_opts(swrContext,out_ch_layout,out_sample,out_sample_rate
                ,in_ch_layout,in_sample,in_sample_rate,0,NULL);
        swr_init(swrContext);
        int got_frame=0;
        int ret;
        int out_channerl_nb = av_get_channel_layout_nb_channels(out_ch_layout);
        LOGE("聲道數量%d ",out_channerl_nb);
        int count=0;
    //    設置音頻緩衝區間 16bit   44100  PCM數據
        uint8_t *out_buffer = (uint8_t *) av_malloc(2 * 44100);
        FILE *fp_pcm = fopen(output, "wb");
        while (av_read_frame(pFormatCtx, packet)>=0) {
    
            ret = avcodec_decode_audio4(codecCtx, frame, &got_frame, packet);
            LOGE("正在解碼%d",count++);
            if (ret < 0) {
                LOGE("解碼完成");
            }
    //        解碼一幀
            if (got_frame > 0) {
                swr_convert(swrContext, &out_buffer, 2 * 44100,
                            (const uint8_t **) frame->data, frame->nb_samples);
                int out_buffer_size=av_samples_get_buffer_size(NULL, out_channerl_nb, frame->nb_samples, out_sample, 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(&swrContext);
        avcodec_close(codecCtx);
        avformat_close_input(&pFormatCtx);
    }
}

 

Native 層回調播放

public class MyPlayer {
    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("native-lib");
    }
    private AudioTrack audioTrack;
    public native void sound(String input,String output);

    //    這個方法  是C進行調用  通道數
    public void createAudio(int sampleRateInHz,int nb_channals) {

        int channaleConfig;
        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傳入音頻數據
    public synchronized  void playTrack(byte[] buffer, int lenth) {
        if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
            audioTrack.write(buffer, 0, lenth);//寫入進來喇叭就直接播放了
        }
    }
}
#include <jni.h>
#include <string>
#include <android/log.h>
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"twy",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"twy",FORMAT,##__VA_ARGS__);

#define MAX_AUDIO_FRME_SIZE 48000 * 4
extern  "C"{
//封裝格式
#include "libavformat/avformat.h"
//解碼
#include "libavcodec/avcodec.h"
//縮放
#include "libswscale/swscale.h"
//重採樣
#include "libswresample/swresample.h"
};
extern "C"
{
    JNIEXPORT void JNICALL
    Java_com_dongnao_ffmpegmusicdemo_MyPlayer_sound(JNIEnv *env, jobject instance, jstring input_,
                                                       jstring output_) {
        const char *input = env->GetStringUTFChars(input_, 0);
        const char *output = env->GetStringUTFChars(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(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 *swrContext = swr_alloc();
    //    音頻格式  重採樣設置參數
        AVSampleFormat in_sample = codecCtx->sample_fmt;
    //    輸出採樣格式
        AVSampleFormat out_sample=AV_SAMPLE_FMT_S16;
    // 輸入採樣率
        int in_sample_rate = codecCtx->sample_rate;
    //    輸出採樣
        int out_sample_rate=44100;
    //    輸入聲道佈局
        uint64_t in_ch_layout=codecCtx->channel_layout;
    //    輸出聲道佈局
        uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
        swr_alloc_set_opts(swrContext,out_ch_layout,out_sample,out_sample_rate
                ,in_ch_layout,in_sample,in_sample_rate,0,NULL);
        swr_init(swrContext);

        //    獲取通道數  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, "createAudio", "(II)V");
        //    反射調用createAudio
        env->CallVoidMethod(instance, createAudio, 44100, out_channer_nb);

        jmethodID audio_write = env->GetMethodID(david_player, "playTrack", "([BI)V");

        int got_frame=0;
        int ret;
        int out_channerl_nb = av_get_channel_layout_nb_channels(out_ch_layout);
        LOGE("聲道數量%d ",out_channerl_nb);
        int count=0;
    //    設置音頻緩衝區間 16bit   44100  PCM數據
        uint8_t *out_buffer = (uint8_t *) av_malloc(2 * 44100);
        FILE *fp_pcm = fopen(output, "wb");
        while (av_read_frame(pFormatCtx, packet)>=0) {

            ret = avcodec_decode_audio4(codecCtx, frame, &got_frame, packet);
            LOGE("正在解碼%d",count++);
            if (ret < 0) {
                LOGE("解碼完成");
            }
    //        解碼一幀
            if (got_frame > 0) {
                swr_convert(swrContext, &out_buffer, 2 * 44100,
                            (const uint8_t **) frame->data, frame->nb_samples);
                int out_buffer_size=av_samples_get_buffer_size(NULL, out_channerl_nb, frame->nb_samples, out_sample, 1);
                //緩衝區的大小
                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);
                //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(&swrContext);
        avcodec_close(codecCtx);
        avformat_close_input(&pFormatCtx);
    }
}

這樣手機就能播放出來

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章