1. 编译
按照教程编译即可: gitee.com/openharmony…
./build ffmpeg
按照教程把编译好的.a文件复制到项目的cpp下面, 记得复制一些必要的.h文件到include里面去
复制源码里面的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… 的代码. 是不是很简单?