1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        FFmpeg音頻處理——音頻混合、拼接、剪切、轉(zhuǎn)碼

        共 22532字,需瀏覽 46分鐘

         ·

        2021-03-10 10:01

        本文是一篇關(guān)于 FFmpeg 音頻處理的文章,轉(zhuǎn)載詳情 可見文章末尾~~

        接觸FFmpeg有一段時間了,它是音視頻開發(fā)的開源庫,幾乎其他所有播放器、直播平臺都基于FFmpeg進行二次開發(fā)。

        本篇文章來總結(jié)下采用FFmpeg進行音頻處理:音頻混合、音頻剪切、音頻拼接與音頻轉(zhuǎn)碼。

        采用android studio進行開發(fā),配置build.gradle文件:

        defaultConfig {
                ......
                externalNativeBuild {
                    cmake {
                        cppFlags ""
                    }
                }
                ndk {
                    abiFilters "armeabi-v7a"
                }
            }

        另外指定cmake文件路徑:


            externalNativeBuild {
                cmake {
                    path "CMakeLists.txt"
                }
            }
            sourceSets {
                main {
                    jniLibs.srcDirs = ['libs']
                    jni.srcDirs = []
                }

        從FFmpeg官網(wǎng)下載源碼,編譯成ffmpeg.so動態(tài)庫,并且導入相關(guān)源文件與頭文件:

        然后配置cMakeLists文件:

        add_library( # Sets the name of the library.
                     audio-handle
         
                     # Sets the library as a shared library.
                     SHARED
         
                     # Provides a relative path to your source file(s).
                     src/main/cpp/ffmpeg_cmd.c
                     src/main/cpp/cmdutils.c
                     src/main/cpp/ffmpeg.c
                     src/main/cpp/ffmpeg_filter.c
                     src/main/cpp/ffmpeg_opt.c)
        add_library( ffmpeg
                     SHARED
                     IMPORTED )
        set_target_properties( ffmpeg
                               PROPERTIES IMPORTED_LOCATION
                               ../../../../libs/armeabi-v7a/libffmpeg.so )
        include_directories(src/main/cpp/include)
        find_library( log-lib
                      log )
        target_link_libraries( audio-handle
                               ffmpeg
                               ${log-lib} )
                               

        調(diào)用FFmpeg命令行進行音頻處理:



            /**
             * 調(diào)用ffmpeg處理音頻
             * @param handleType handleType
             */
            private void doHandleAudio(int handleType){
                String[] commandLine = null;
                switch (handleType){
                    case 0://轉(zhuǎn)碼
                        String transformFile = PATH + File.separator + "transform.aac";
                        commandLine = FFmpegUtil.transformAudio(srcFile, transformFile);
                        break;
                    case 1://剪切
                        String cutFile = PATH + File.separator + "cut.mp3";
                        commandLine = FFmpegUtil.cutAudio(srcFile, 10, 15, cutFile);
                        break;
                    case 2://合并
                        String concatFile = PATH + File.separator + "concat.mp3";
                        commandLine = FFmpegUtil.concatAudio(srcFile, appendFile, concatFile);
                        break;
                    case 3://混合
                        String mixFile = PATH + File.separator + "mix.aac";
                        commandLine = FFmpegUtil.mixAudio(srcFile, appendFile, mixFile);
                        break;
                    default:
                        break;
                }
                executeFFmpegCmd(commandLine);
            }

        其中,音頻混音、合并、剪切和轉(zhuǎn)碼的FFmpeg命令行的拼接如下:


         

            /**
             * 使用ffmpeg命令行進行音頻轉(zhuǎn)碼
             * @param srcFile 源文件
             * @param targetFile 目標文件(后綴指定轉(zhuǎn)碼格式)
             * @return 轉(zhuǎn)碼后的文件
             */
            public static String[] transformAudio(String srcFile, String targetFile){
                String transformAudioCmd = "ffmpeg -i %s %s";
                transformAudioCmd = String.format(transformAudioCmd, srcFile, targetFile);
                return transformAudioCmd.split(" ");//以空格分割為字符串數(shù)組
            }
         
            /**
             * 使用ffmpeg命令行進行音頻剪切
             * @param srcFile 源文件
             * @param startTime 剪切的開始時間(單位為秒)
             * @param duration 剪切時長(單位為秒)
             * @param targetFile 目標文件
             * @return 剪切后的文件
             */
            @SuppressLint("DefaultLocale")
            public static  String[] cutAudio(String srcFile, int startTime, int duration, String targetFile){
                String cutAudioCmd = "ffmpeg -i %s -ss %d -t %d %s";
                cutAudioCmd = String.format(cutAudioCmd, srcFile, startTime, duration, targetFile);
                return cutAudioCmd.split(" ");//以空格分割為字符串數(shù)組
            }
         
            /**
             * 使用ffmpeg命令行進行音頻合并
             * @param srcFile 源文件
             * @param appendFile 待追加的文件
             * @param targetFile 目標文件
             * @return 合并后的文件
             */
            public static  String[] concatAudio(String srcFile, String appendFile, String targetFile){
                String concatAudioCmd = "ffmpeg -i concat:%s|%s -acodec copy %s";
                concatAudioCmd = String.format(concatAudioCmd, srcFile, appendFile, targetFile);
                return concatAudioCmd.split(" ");//以空格分割為字符串數(shù)組
            }
         
            /**
             * 使用ffmpeg命令行進行音頻混合
             * @param srcFile 源文件
             * @param mixFile 待混合文件
             * @param targetFile 目標文件
             * @return 混合后的文件
             */
            public static  String[] mixAudio(String srcFile, String mixFile, String targetFile){
                String mixAudioCmd = "ffmpeg -i %s -i %s -filter_complex amix=inputs=2:duration=first -strict -2 %s";
                mixAudioCmd = String.format(mixAudioCmd, srcFile, mixFile, targetFile);
                return mixAudioCmd.split(" ");//以空格分割為字符串數(shù)組
            }
            

        FFmpeg處理混音的公式如下,其中sample1為源文件采樣率、sample2為待混合文件采樣率:


        混音公式:value = sample1 + sample2 - (sample1 * sample2 / (pow(2, 16-1) - 1))

        開啟子線程,調(diào)用native方法進行音頻處理:


            public static void execute(final String[] commands, final OnHandleListener onHandleListener){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        if(onHandleListener != null){
                            onHandleListener.onBegin();
                        }
                        //調(diào)用ffmpeg進行處理
                        int result = handle(commands);
                        if(onHandleListener != null){
                            onHandleListener.onEnd(result);
                        }
                    }
                }).start();
            }
            private native static int handle(String[] commands);
            

        關(guān)鍵的native方法,是把java傳入的字符串數(shù)組轉(zhuǎn)成二級指針數(shù)組,然后調(diào)用FFmpeg源碼中的run方法:


         

        JNIEXPORT jint JNICALL Java_com_frank_ffmpeg_FFmpegCmd_handle
        (JNIEnv *env, jclass obj, jobjectArray commands){
            int argc = (*env)->GetArrayLength(env, commands);
            char **argv = (char**)malloc(argc * sizeof(char*));
            int i;
            int result;
            for (i = 0; i < argc; i++) {
                jstring jstr = (jstring) (*env)->GetObjectArrayElement(env, commands, i);
                char* temp = (char*) (*env)->GetStringUTFChars(env, jstr, 0);
                argv[i] = malloc(1024);
                strcpy(argv[i], temp);
                (*env)->ReleaseStringUTFChars(env, jstr, temp);
            }
            //執(zhí)行ffmpeg命令
            result =  run(argc, argv);
            //釋放內(nèi)存
            for (i = 0; i < argc; i++) {
                free(argv[i]);
            }
            free(argv);
            return result;
        }

        關(guān)于FFmpeg的run方法的源碼如下,中間有部分省略:

        int run(int argc, char **argv)
        {
            /****************省略********************/
            //注冊各個模塊
            avcodec_register_all();
        #if CONFIG_AVDEVICE
            avdevice_register_all();
        #endif
            avfilter_register_all();
            av_register_all();
            avformat_network_init();
            show_banner(argc, argv, options);
            term_init();
            /****************省略********************/
            //解析命令選項與打開輸入輸出文件
            int ret = ffmpeg_parse_options(argc, argv);
            if (ret < 0)
                exit_program(1);
            /****************省略********************/
            //文件轉(zhuǎn)換
            if (transcode() < 0)
                exit_program(1);
            /****************省略********************/
            //退出程序操作:關(guān)閉文件、釋放內(nèi)存
            exit_program(received_nb_signals ? 255 : main_return_code);
            ffmpeg_cleanup(0);
        }

        其中,最關(guān)鍵的是文件轉(zhuǎn)換部分,源碼如下:



        static int transcode(void)
        {
            int ret, i;
            AVFormatContext *os;
            OutputStream *ost;
            InputStream *ist;
            int64_t timer_start;
            int64_t total_packets_written = 0;
            //轉(zhuǎn)碼方法初始化
            ret = transcode_init();
            if (ret < 0)
                goto fail;
         
            if (stdin_interaction) {
                av_log(NULL, AV_LOG_INFO, "Press [q] to stop, [?] for help\n");
            }
            timer_start = av_gettime_relative();
         
        #if HAVE_PTHREADS
            if ((ret = init_input_threads()) < 0)
                goto fail;
        #endif
            //transcode循環(huán)處理
            while (!received_sigterm) {
                int64_t cur_time= av_gettime_relative();
         
                //如果遇到"q"命令,則退出循環(huán)
                if (stdin_interaction)
                    if (check_keyboard_interaction(cur_time) < 0)
                        break;
         
                //判斷是否還有輸出流
                if (!need_output()) {
                    av_log(NULL, AV_LOG_VERBOSE, "No more output streams to write to, finishing.\n");
                    break;
                }
         
                ret = transcode_step();
                if (ret < 0 && ret != AVERROR_EOF) {
                    char errbuf[128];
                    av_strerror(ret, errbuf, sizeof(errbuf));
         
                    av_log(NULL, AV_LOG_ERROR, "Error while filtering: %s\n", errbuf);
                    break;
                }
         
                //打印音視頻流信息
                print_report(0, timer_start, cur_time);
            }
        #if HAVE_PTHREADS
            free_input_threads();
        #endif
         
            //文件末尾最后一個stream,刷新解碼器buffer
            for (i = 0; i < nb_input_streams; i++) {
                ist = input_streams[i];
                if (!input_files[ist->file_index]->eof_reached && ist->decoding_needed) {
                    process_input_packet(ist, NULL, 0);
                }
            }
            flush_encoders();
            term_exit();
         
            //寫文件尾,關(guān)閉文件
            for (i = 0; i < nb_output_files; i++) {
                os = output_files[i]->ctx;
                if ((ret = av_write_trailer(os)) < 0) {
                    av_log(NULL, AV_LOG_ERROR, "Error writing trailer of %s: %s", os->filename, av_err2str(ret));
                    if (exit_on_error)
                        exit_program(1);
                }
            }
         
            //關(guān)閉所有編碼器
            for (i = 0; i < nb_output_streams; i++) {
                ost = output_streams[i];
                if (ost->encoding_needed) {
                    av_freep(&ost->enc_ctx->stats_in);
                }
                total_packets_written += ost->packets_written;
            }
         
            if (!total_packets_written && (abort_on_flags & ABORT_ON_FLAG_EMPTY_OUTPUT)) {
                av_log(NULL, AV_LOG_FATAL, "Empty output\n");
                exit_program(1);
            }
         
            //關(guān)閉所有解碼器
            for (i = 0; i < nb_input_streams; i++) {
                ist = input_streams[i];
                if (ist->decoding_needed) {
                    avcodec_close(ist->dec_ctx);
                    if (ist->hwaccel_uninit)
                        ist->hwaccel_uninit(ist->dec_ctx);
                }
            }
         
            //省略最后的釋放內(nèi)存
            return ret;
        }

        好了,使用FFmpeg進行音頻剪切、混音、拼接與轉(zhuǎn)碼介紹完畢。

        如果各位有什么問題或者建議,歡迎交流。

        轉(zhuǎn)載自:https://blog.csdn.net/u011686167/article/details/79135240

        文章源碼地址為:https://github.com/xufuji456/FFmpegAndroid


        技術(shù)交流,歡迎加我微信:ezglumes ,拉你入技術(shù)交流群。

        推薦閱讀:

        音視頻面試基礎(chǔ)題

        OpenGL ES 學習資源分享

        開通專輯 | 細數(shù)那些年寫過的技術(shù)文章專輯

        NDK 學習進階免費視頻來了

        推薦幾個堪稱教科書級別的 Android 音視頻入門項目

        覺得不錯,點個在看唄~

        瀏覽 89
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            成人网站在线观看免费 | tk调教折磨高潮 | 欧美精品粉嫩小泬188 | av偷拍自拍 | 日韩精品啪啪 | 三级伦理片在线电影 | 青青草这里只有精品 | 国产看逼 | 免费无遮挡 视频网站用片海 | 国产原创无码 |