鸿蒙next中使用ffmpeg的命令行

2,717 阅读3分钟

1. 编译

按照教程编译即可: gitee.com/openharmony…

./build ffmpeg

按照教程把编译好的.a文件复制到项目的cpp下面, 记得复制一些必要的.h文件到include里面去

image.png

复制源码里面的fftool下面的

ffmpeg.h
cmdutils.h
cmdutils.c
ffmpeg.c
ffmpeg_filter.c
ffmpeg_opt.c
ffmpeg_hw.c

复制编译好的config.h, 差不多是下面的路径

tpc_c_cplusplus/thirdparty/FFmpeg/FFmpeg-arm64-v8a-build/FFmpeg-n4.1/config.h

然后新建一个exception.h

#ifndef EXCEPTION_H
#define EXCEPTION_H

#include <setjmp.h>

/** Holds information to implement exception handling. */
extern __thread jmp_buf ex_buf__;

#endif // EXCEPTION_H

继续新建exception.c

#include <stdio.h>
#include <setjmp.h>

/** Holds information to implement exception handling. */
__thread jmp_buf ex_buf__;

2. 修改源码

2.1 修复ffmpeg.c, 把int main方法改名字, 记得在头文件ffmpeg.h里面定义

int exe_ffmpeg_cmd(int argc, char **argv)

2.2 在ffmpeg.c的顶部添加

extern __thread volatile int longjmp_value;

2.3 修改cmdutils.c

void exit_program(int ret) {
    if (program_exit)
        program_exit(ret);
        
    // exit disabled and replaced with longjmp, exit value stored in longjmp_value
    // exit(ret);
    longjmp_value = ret;
    longjmp(ex_buf__, ret);
}

2.4 修改ffmpeg.c的exe_ffmpeg_cmd方法

int exe_ffmpeg_cmd(int argc, char **argv, Callbacks *callback) {
    int i, ret;
    BenchmarkTimeStamps ti;

    int savedCode = setjmp(ex_buf__);
    if (savedCode == 0) {
        init_dynload();

        register_exit(ffmpeg_cleanup);

        setvbuf(stderr, NULL, _IONBF, 0); /* win32 runtime needs this */

        av_log_set_flags(AV_LOG_SKIP_REPEATED);
        parse_loglevel(argc, argv, options);

        if (argc > 1 && !strcmp(argv[1], "-d")) {
            run_as_daemon = 1;
            av_log_set_callback(log_callback_null);
            argc--;
            argv++;
        }

#if CONFIG_AVDEVICE
        avdevice_register_all();
#endif
        avformat_network_init();

        show_banner(argc, argv, options);

        /* parse options and open all input/output files */
        ret = ffmpeg_parse_options(argc, argv);
        if (ret < 0)
            exit_program(ret);

        if (nb_output_files <= 0 && nb_input_files == 0) {
            show_usage();
            av_log(NULL, AV_LOG_WARNING, "Use -h to get full help or, even better, run 'man %s'\n", program_name);
            exit_program(1);
        }

        /* file converter / grab */
        if (nb_output_files <= 0) {
            av_log(NULL, AV_LOG_FATAL, "At least one output file must be specified\n");
            exit_program(1);
        }

        for (i = 0; i < nb_output_files; i++) {
            if (strcmp(output_files[i]->ctx->oformat->name, "rtp"))
                want_sdp = 0;
        }

        current_time = ti = get_benchmark_time_stamps();
        ret = transcode();
        if (ret < 0) {
            exit_program(ret);
        }
        if (do_benchmark) {
            int64_t utime, stime, rtime;
            current_time = get_benchmark_time_stamps();
            utime = current_time.user_usec - ti.user_usec;
            stime = current_time.sys_usec - ti.sys_usec;
            rtime = current_time.real_usec - ti.real_usec;
            av_log(NULL, AV_LOG_INFO, "bench: utime=%0.3fs stime=%0.3fs rtime=%0.3fs\n", utime / 1000000.0,
                   stime / 1000000.0, rtime / 1000000.0);
        }
        av_log(NULL, AV_LOG_DEBUG, "%" PRIu64 " frames successfully decoded, %" PRIu64 " decoding errors\n",
               decode_error_stat[0], decode_error_stat[1]);
        if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1])
            exit_program(69);

        exit_program(received_nb_signals ? 255 : main_return_code);
    } else {
        main_return_code = (received_nb_signals) ? 255 : longjmp_value;
    }
    return main_return_code;
}

3. 精髓

整个修改的精髓在于setjmp和longjmp, 这俩是一对, 长跳转. 每次程序跑到exit_program(ret)方法的时候, 本来是要结束(闪退)程序的. 但是我们直接注释掉原来的代码, 理由longjmp跳转到设置setjmp的地方去, 从而"优雅"的结束调用. 类似goto?

4. 使用

void log_call_back(void *ptr, int level, const char *fmt, va_list vl) {
    static int print_prefix = 1;
    static int count;
    static char prev[1024];
    char line[1024];
    static int is_atty;
    av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix);
    strcpy(prev, line);
    OH_LOG_ERROR(LOG_APP, "========> %{public}s", line);
}

void showLog(bool show) {
    if (show) {
        av_log_set_callback(log_call_back);
    }
}

int executeFFmpegCommandAPP(std::string uuid, int cmdLen, std::vector<std::string> argv) {
    char **argv1 = vector_to_argv(argv);

    CallBackInfo onActionListener;
    // int ret = exe_ffmpeg_cmd(cmdLen, argv1, (int64_t) (&onActionListener), progressCallBack, -1);
    onActionListener.onFFmpegProgress = aki::JSBind::GetJSFunction(uuid + "_onFFmpegProgress");
    onActionListener.onFFmpegFail = aki::JSBind::GetJSFunction(uuid + "_onFFmpegFail");
    onActionListener.onFFmpegSuccess = aki::JSBind::GetJSFunction(uuid + "_onFFmpegSuccess");

    Callbacks callbacks = {
        .onFFmpegProgress = onFFmpegProgress, 
        .onFFmpegFail = onFFmpegFail, 
        .onFFmpegSuccess = onFFmpegSuccess
    };

    int ret = exe_ffmpeg_cmd(cmdLen, argv1, &callbacks);
    if (ret != 0) {
        char err[1024] = {0};
        int nRet = av_strerror(ret, err, 1024);
        onActionListener.onFFmpegFail->Invoke<void>(ret, err);
    } else {
        onActionListener.onFFmpegSuccess->Invoke<void>();
    }
    
    for (int i = 0; i < cmdLen; ++i) {
        free(argv1[i]);
    }
    return ret;
}

JSBIND_ADDON(ffmpegutils)

JSBIND_GLOBAL() {
    JSBIND_PFUNCTION(executeFFmpegCommandAPP);
    JSBIND_FUNCTION(showLog);
}

在ets里面调用

import { executeFFmpegCommandAPP } from '../../../cpp/types/ffmpegutils'
import { logE } from '../tools'
import libAddon from 'libffmpegutils.so'
import { RandomUtil } from '@pura/harmony-utils';

export interface FFmpegCommandOptions {
  cmds: Array<string>;
  onFFmpegProgress: (progress: number) => void;
  onFFmpegFail: (code: number, msg: string) => void;
  onFFmpegSuccess: () => void;
}

export class FFMpegUtils {
  static executeFFmpegCommand(options: FFmpegCommandOptions): Promise<number> {
    let uuid = RandomUtil.generateUUID32()
    libAddon.JSBind.bindFunction(uuid + "_onFFmpegProgress", options.onFFmpegProgress)
    libAddon.JSBind.bindFunction(uuid + "_onFFmpegFail", options.onFFmpegFail)
    libAddon.JSBind.bindFunction(uuid + "_onFFmpegSuccess", options.onFFmpegSuccess)

    logE(options.cmds.join(' '))
    return new Promise<number>((resolve, reject) => {
      try {
        libAddon.executeFFmpegCommandAPP(uuid, options.cmds.length, options.cmds).then((code: number) => {
          resolve(code)
        }).catch((err: Error) => {
          reject(err)
        })
      } catch (e) {
        reject(e)
      }
    })
  }
}

cmd里面直接这样用

ffmpeg -i input1.mp3 output1.aac

在napi_init.cpp里面, 用到了gitee.com/openharmony… 的代码. 是不是很简单?