设计说明
30 Oct 2024
Read time: 16 minute(s)
源码说明
本模块源码位于 source/artinchip/aic-mpp/middle_media 下,目录结构如下
aic-mpp
|--middle-media
|-base
| |-inlcude
| | |-aic_message.h //定义公共消息接口
| | |-aic_muxer.h //定义 muxer 接口
| | |-aic_stream.h //定义 stream 接口
| |
| |-message //消息接口具体实现
| |-muxer //muxer 接口具体实现
| |-stream //stream 接口具体实现
|
|-component //Recorder 组件
| |-src //middle media component 各组件具体实现
| | |-mm_muxer_component.c //封装组件,编码后封装成指定格式
| | |-mm_venc_component.c //视频编码组件,输入 YUV 数据,输出编码后的视频图像
| | |-mm_vin_component.c //视频输入组件,支持 DVP 和 文件输入
| |-mm_core.c //middle media component 公共核心接口具体实现
| |-inlcude //middle media component 公共头文件
| |-mm_component.h //middle media 各组件通用接口
| |-mm_core.h //middle media 组件对外核心接口
| |-mm_index.h //middle media 组件公共结构体
|
|-recorder
|-inlcude
| |-aic_recorder.h //recorder 接口定义
|
|aic_recorder.c //recorder 接口实现
模块架构
recorder 在系统架构中的层次如下图
-
recorder 是中间件,向 App 提供 recorder 接口
-
recorder 视频编码调用 mjpeg_encoder
-
recorder 音频编码调用 暂未实现
-
recorder 视频输入操作 DVP driver 和 MPP VIN 提供的接口
-
recorder 音频输入操作 暂未实现
-
recorder 内部实现封装的功能
-
recorder 模块架构如下图,分为 3 层
-
第 1 层,recorder interface ,向上提供 recorder 接口,向下调用 mm 组件实现 recorder 功能。
-
第 2 层,component,提供统一的组件操作接口。每个组件完成特定的功能,比如视频编码,视频渲染等。
-
第 3 层,Base,提供 muxer,stream,message 接口和实现。
-
recorder 提供封装的功能,比如 MP4 封装
-
stream 提供封装流协议的功能,本地文件也看作是一种流协议,目前也只支持本地文件
-
message 为组件传递消息提供支持
-
目前支持 MP4 封装的本地文件,后续扩展其他流协议和封装格式,通过增加相应的 recorder 和 stream 实现
-
数据结构设计
//视频编码配置
struct video_encoding_config {
enum mpp_codec_type codec_type;
s32 out_width;
s32 out_height;
s32 out_bit_rate;
s32 out_frame_rate;
s32 out_qfactor;
// now must be out_width = in_width and out_height= in_height
// case mjpeg encoder has no scale function
s32 in_width;
s32 in_height;
s32 in_pix_fomat;
};
//音频编码配置
struct audio_encoding_config {
enum aic_audio_codec_type codec_type;
int out_bitrate;
int out_samplerate;
int out_channels;
int out_bits_per_sample;
int in_samplerate;
int in_channels;
int in_bits_per_sample;
};
//录像配置
struct aic_recorder_config {
int file_duration; // unit:second one file duration
int file_num; // 0-loop, >0 record file_num and then stop recording.
int file_muxer_type; // only support mp4
int qfactor;
s8 has_video;
s8 has_audio;
struct audio_encoding_config audio_config;
struct video_encoding_config video_config;
};
//抓拍信息
struct aic_record_snapshot_info {
s8 *file_path;
};
//录像事件类型
enum aic_recorder_event {
AIC_RECORDER_EVENT_NEED_NEXT_FILE = 0,
AIC_RECORDER_EVENT_COMPLETE, // when file_num > 0,record file_num then send this event
AIC_RECORDER_EVENT_NO_SPACE,
AIC_RECORDER_EVENT_RELEASE_VIDEO_BUFFER // notify app input_frame has used.
};
//视频输入源
enum aic_recorder_vin_type {
AIC_RECORDER_VIN_FILE = 0,
AIC_RECORDER_VIN_DVP,
AIC_RECORDER_VIN_USB,
};
//事件回调函数
typedef s32 (*event_handler)(void *app_data, s32 event, s32 data1, s32 data2);
接口设计
函数原型 | struct aic_recorder *aic_recorder_create(void) |
---|---|
功能说明 | 创建 recorder 对象 |
参数定义 |
无
|
返回值 | recorder 对象 |
注意事项 | - |
函数原型 | s32 aic_recorder_destroy(struct aic_recorder *recorder) |
---|---|
功能说明 | 销毁 recorder 对象 |
参数定义 |
recorder:recorder 对象
|
返回值 |
0:成功,其他:失败
|
注意事项 | 在 IDLE 和 STOPPED 状态起作用 |
函数原型 | s32 aic_recorder_init(struct aic_recorder *recorder, struct aic_recorder_config *recorder_config) |
---|---|
功能说明 | 初始化 recorder 对象 |
参数定义 |
recorder:recorder 对象 recorder_config: recorder 配置
|
返回值 |
0:成功,其他:失败
|
注意事项 | 在 IDLE 和 STOPPED 状态起作用 |
函数原型 | s32 aic_recorder_start(struct aic_recorder *recorder) |
---|---|
功能说明 | 启动录像,创建音视频编码线程和音视频输入线程 |
参数定义 | recorder:recorder 对象 |
返回值 |
0:成功,其他:失败
|
注意事项 | 在 PREPARED 状态起作用 |
函数原型 | s32 aic_recorder_stop(struct aic_recorder *recorder) |
---|---|
功能说明 | 停止录像,释放音视频编码线程和音视频输入线程 |
参数定义 | recorder:recorder 对象 |
返回值 |
0:成功,其他:失败
|
注意事项 | 任意状态可以调用该函数来停止录像。切换文件时一定要调用 aic_recorder_stop。 |
函数原型 | s32 aic_recorder_set_input_file_path(struct aic_recorder *recorder, char *video_uri, char *audio_uri) |
---|---|
功能说明 | 视频输入源为文件时,设置文件路径 |
参数定义 |
recorder:recorder video_uri:视频源文件路径
audio_uri:音频源文件路径
|
返回值 |
0:成功,其他:失败
|
注意事项 | 在 IDLE 和 STOPPED 状态起作用 |
函数原型 | s32 aic_recorder_set_output_file_path(struct aic_recorder *recorder,char *uri) |
---|---|
功能说明 | 设置输出录像文件路径 |
参数定义 |
recorder:recorder 对象,uri:媒体文件路径
|
返回值 |
0:成功,其他:失败
|
注意事项 | 在 IDLE 和 STOPPED 状态起作用 |
函数原型 | s32 aic_recorder_set_event_callback(struct aic_recorder *recorder, event_handler event_handle) |
---|---|
功能说明 | 设置录像事件回调函数 |
参数定义 |
recorder:recorder 对象,event_handle:监听事件函数
|
返回值 |
0:成功,其他:失败
|
注意事项 | 一定要注册,录像结束通过回调函数通知 |
函数原型 | s32 aic_recorder_snapshot(struct aic_recorder *recorder, struct aic_record_snapshot_info *snapshot_info) |
---|---|
功能说明 | 触发一次抓拍事件 |
参数定义 |
recorder:recorder 对象,snapshot_info:抓拍信息
|
返回值 |
0:成功,其他:失败
|
注意事项 | 启动录像期间抓拍 |
函数原型 | s32 aic_recorder_set_vin_type(struct aic_recorder *recorder, enum aic_recorder_vin_type type); |
---|---|
功能说明 | 设置视频输入源类型 |
参数定义 |
recorder:recorder 对象,type:视频输入源类型
|
返回值 |
0:成功,其他:失败
|
注意事项 | 无 |
APP Demo
static void print_help(const char* prog)
{
printf("name: %s\n", prog);
printf("Compile time: %s\n", __TIME__);
printf("Usage: recoder_demo [options]:\n"
"\t-i video input source\n"
"\t-c recoder config file\n"
"\t-h help\n\n"
"Example1: recoder_demo -i dvp -c /sdcard/recoder.json\n"
"Example2: recoder_demo -i file -c /sdcard/recoder.json\n");
}
char *read_file(const char *filename)
{
FILE *file = NULL;
long length = 0;
char *content = NULL;
size_t read_chars = 0;
/* open in read binary mode */
file = fopen(filename, "rb");
if (file == NULL) {
goto cleanup;
}
/* get the length */
if (fseek(file, 0, SEEK_END) != 0) {
goto cleanup;
}
length = ftell(file);
if (length < 0) {
goto cleanup;
}
if (fseek(file, 0, SEEK_SET) != 0) {
goto cleanup;
}
/* allocate content buffer */
content = (char *)malloc((size_t)length + sizeof(""));
if (content == NULL) {
goto cleanup;
}
/* read the file into memory */
read_chars = fread(content, sizeof(char), (size_t)length, file);
if ((long)read_chars != length) {
free(content);
content = NULL;
goto cleanup;
}
content[read_chars] = '\0';
cleanup:
if (file != NULL) {
fclose(file);
}
return content;
}
static cJSON *parse_file(const char *filename)
{
cJSON *parsed = NULL;
char *content = read_file(filename);
parsed = cJSON_Parse(content);
if (content != NULL) {
free(content);
}
return parsed;
}
struct recorder_context {
struct aic_recorder *recorder;
enum aic_recorder_vin_type vin_source_type;
char config_file_path[256];
char video_in_file_path[256];
char audio_in_file_path[256];
char output_file_path[256];
char capture_file_path[256];
struct aic_recorder_config config;
};
static int g_recorder_flag = 0;
static struct recorder_context *g_recorder_cxt = NULL;
static s32 event_handle(void *app_data, s32 event, s32 data1, s32 data2)
{
int ret = 0;
struct recorder_context *recorder_cxt = (struct recorder_context *)app_data;
static int file_index = 0;
char file_path[512] = {0};
switch (event) {
case AIC_RECORDER_EVENT_NEED_NEXT_FILE:
// set recorder file name
snprintf(file_path, sizeof(file_path), "%s-%d.mp4",
recorder_cxt->output_file_path, file_index++);
aic_recorder_set_output_file_path(recorder_cxt->recorder, file_path);
printf("set recorder file:%s\n", file_path);
break;
case AIC_RECORDER_EVENT_COMPLETE:
g_recorder_flag = 1;
break;
case AIC_RECORDER_EVENT_NO_SPACE:
break;
default:
break;
}
return ret;
}
int parse_config_file(char *config_file, struct recorder_context *recorder_cxt)
{
int ret = 0;
cJSON *cjson = NULL;
cJSON *root = NULL;
if (!config_file || !recorder_cxt) {
ret = -1;
goto _EXIT;
}
root = parse_file(config_file);
if (!root) {
loge("parse_file error %s!!!", config_file);
ret = -1;
goto _EXIT;
}
cjson = cJSON_GetObjectItem(root, "video_in_file");
if (!cjson) {
loge("no video_in_file error");
ret = -1;
goto _EXIT;
}
strcpy(recorder_cxt->video_in_file_path, cjson->valuestring);
cjson = cJSON_GetObjectItem(root, "audio_in_file");
if (!cjson) {
strcpy(recorder_cxt->audio_in_file_path, cjson->valuestring);
}
cjson = cJSON_GetObjectItem(root, "output_file");
if (!cjson) {
loge("no output_file error");
ret = -1;
goto _EXIT;
}
strcpy(recorder_cxt->output_file_path, cjson->valuestring);
cjson = cJSON_GetObjectItem(root, "file_duration");
if (cjson) {
recorder_cxt->config.file_duration = cjson->valueint * 1000;
}
cjson = cJSON_GetObjectItem(root, "file_num");
if (cjson) {
recorder_cxt->config.file_num = cjson->valueint;
}
cjson = cJSON_GetObjectItem(root, "qfactor");
if (cjson) {
recorder_cxt->config.qfactor = cjson->valueint;
}
cjson = cJSON_GetObjectItem(root, "video");
if (cjson) {
int enable = cJSON_GetObjectItem(cjson, "enable")->valueint;
if (enable == 1) {
recorder_cxt->config.has_video = 1;
}
recorder_cxt->config.video_config.codec_type =
cJSON_GetObjectItem(cjson, "codec_type")->valueint;
printf("codec_type:0x%x\n", recorder_cxt->config.video_config.codec_type);
if (recorder_cxt->config.video_config.codec_type != MPP_CODEC_VIDEO_DECODER_MJPEG) {
ret = -1;
loge("only support MPP_CODEC_VIDEO_DECODER_MJPEG");
g_recorder_flag = 1;
goto _EXIT;
}
recorder_cxt->config.video_config.out_width =
cJSON_GetObjectItem(cjson, "out_width")->valueint;
recorder_cxt->config.video_config.out_height =
cJSON_GetObjectItem(cjson, "out_height")->valueint;
recorder_cxt->config.video_config.out_frame_rate =
cJSON_GetObjectItem(cjson, "out_framerate")->valueint;
recorder_cxt->config.video_config.out_bit_rate =
cJSON_GetObjectItem(cjson, "out_bitrate")->valueint;
recorder_cxt->config.video_config.in_width =
cJSON_GetObjectItem(cjson, "in_width")->valueint;
recorder_cxt->config.video_config.in_height =
cJSON_GetObjectItem(cjson, "in_height")->valueint;
recorder_cxt->config.video_config.in_pix_fomat =
cJSON_GetObjectItem(cjson, "in_pix_format")->valueint;
}
cjson = cJSON_GetObjectItem(root, "audio");
if (cjson) {
int enable = cJSON_GetObjectItem(cjson, "enable")->valueint;
if (enable == 1) {
recorder_cxt->config.has_audio = 1;
}
recorder_cxt->config.audio_config.codec_type =
cJSON_GetObjectItem(cjson, "codec_type")->valueint;
recorder_cxt->config.audio_config.out_bitrate =
cJSON_GetObjectItem(cjson, "out_bitrate")->valueint;
recorder_cxt->config.audio_config.out_samplerate =
cJSON_GetObjectItem(cjson, "out_samplerate")->valueint;
recorder_cxt->config.audio_config.out_channels =
cJSON_GetObjectItem(cjson, "out_channels")->valueint;
recorder_cxt->config.audio_config.out_bits_per_sample =
cJSON_GetObjectItem(cjson, "out_bits_per_sample")->valueint;
recorder_cxt->config.audio_config.in_samplerate =
cJSON_GetObjectItem(cjson, "in_samplerate")->valueint;
recorder_cxt->config.audio_config.in_channels =
cJSON_GetObjectItem(cjson, "in_channels")->valueint;
recorder_cxt->config.audio_config.in_bits_per_sample =
cJSON_GetObjectItem(cjson, "in_bits_per_sample")->valueint;
}
_EXIT:
if (root) {
cJSON_Delete(root);
}
return ret;
}
static int parse_options(struct recorder_context *recoder_ctx, int cnt, char **options)
{
int argc = cnt;
char **argv = options;
struct recorder_context *ctx = recoder_ctx;
int opt;
if (!ctx || argc == 0 || !argv) {
loge("para error !!!");
return -1;
}
optind = 0;
while (1) {
opt = getopt(argc, argv, "i:c:h");
if (opt == -1) {
break;
}
switch (opt) {
case 'i':
if (strcmp(optarg, "dvp") == 0) {
ctx->vin_source_type = AIC_RECORDER_VIN_DVP;
} else {
ctx->vin_source_type = AIC_RECORDER_VIN_FILE;
}
break;
case 'c':
strcpy(ctx->config_file_path, optarg);
break;
case 'h':
default:
print_help(argv[0]);
return -1;
}
}
return 0;
}
static void show_cpu_usage()
{
#if defined(LPKG_USING_CPU_USAGE) && defined(RECODER_DEMO_PRINT_CPU)
static int index = 0;
char data_str[64];
float value = 0.0;
if (index++ % 30 == 0) {
value = cpu_load_average();
#ifdef AIC_PRINT_FLOAT_CUSTOM
int cpu_i;
int cpu_frac;
cpu_i = (int)value;
cpu_frac = (value - cpu_i) * 100;
snprintf(data_str, sizeof(data_str), "%d.%02d\n", cpu_i, cpu_frac);
#else
snprintf(data_str, sizeof(data_str), "%.2f\n", value);
#endif
printf("cpu_loading:%s\n",data_str);
}
#endif
}
static void *test_recorder_thread(void *arg)
{
struct recorder_context *recorder_cxt = (struct recorder_context *)arg;
while (!g_recorder_flag) {
show_cpu_usage();
usleep(100*1000);
}
if (recorder_cxt && recorder_cxt->recorder) {
aic_recorder_stop(recorder_cxt->recorder);
aic_recorder_destroy(recorder_cxt->recorder);
recorder_cxt->recorder = NULL;
}
if (recorder_cxt) {
free(recorder_cxt);
recorder_cxt = NULL;
}
printf("test_recorder_thread exit\n");
return NULL;
}
#define BUFFER_LEN 32
int recorder_demo_test(int argc, char *argv[])
{
int ret = 0;
pthread_attr_t attr;
pthread_t thread_id;
struct recorder_context *recorder_cxt = NULL;
g_recorder_flag = 0;
recorder_cxt = malloc(sizeof(struct recorder_context));
if (!recorder_cxt) {
loge("malloc error");
return -1;
}
memset(recorder_cxt, 0x00, sizeof(struct recorder_context));
if (parse_options(recorder_cxt, argc, argv)) {
goto _EXIT;
}
g_recorder_cxt = recorder_cxt;
if (parse_config_file(recorder_cxt->config_file_path, recorder_cxt)) {
loge("parse_config_file %s error", recorder_cxt->config_file_path);
goto _EXIT;
}
#ifdef _THREAD_TRACE_INFO_
memset(&thread_trace_infos,0x00,sizeof(struct thread_trace_info));
for (int i = 0; i < 6 ;i++) {
snprintf(thread_trace_infos[i].thread_name,sizeof(thread_trace_infos[i].thread_name),"%s%02d","pth",i);
printf("%s\n",thread_trace_infos[i].thread_name);
}
rt_scheduler_sethook(hook_of_scheduler);
#endif
recorder_cxt->recorder = aic_recorder_create();
if (!recorder_cxt->recorder) {
loge("aic_recorder_create error");
goto _EXIT;
}
if (aic_recorder_set_event_callback(recorder_cxt->recorder,
recorder_cxt, event_handle)) {
loge("aic_recorder_set_event_callback error");
goto _EXIT;
}
if (aic_recorder_init(recorder_cxt->recorder, &recorder_cxt->config)) {
loge("aic_recorder_init error");
goto _EXIT;
}
aic_recorder_set_vin_type(recorder_cxt->recorder, recorder_cxt->vin_source_type);
aic_recorder_set_input_file_path(recorder_cxt->recorder, recorder_cxt->video_in_file_path, NULL);
if (aic_recorder_start(recorder_cxt->recorder)) {
loge("aic_recorder_start error");
goto _EXIT;
}
pthread_attr_init(&attr);
attr.stacksize = 2 * 1024;
attr.schedparam.sched_priority = 30;
ret = pthread_create(&thread_id, &attr, test_recorder_thread, recorder_cxt);
if (ret) {
loge("create test_recorder_thread failed\n");
}
#include "msh.h"
msh_exec("ps \n", strlen("ps \n"));
return ret;
_EXIT:
if (recorder_cxt && recorder_cxt->recorder) {
aic_recorder_stop(recorder_cxt->recorder);
aic_recorder_destroy(recorder_cxt->recorder);
recorder_cxt->recorder = NULL;
}
if (recorder_cxt) {
free(recorder_cxt);
recorder_cxt = NULL;
}
return ret;
}
MSH_CMD_EXPORT_ALIAS(recorder_demo_test, recorder_demo, recorder demo);
int recorder_demo_cmd(int argc, char *argv[])
{
int ret = 0;
static int capture_count = 0;
struct recorder_context *recorder_cxt = g_recorder_cxt;
if (argc < 1) {
return -1;
}
if (strcmp(argv[1], "stop") == 0) {
g_recorder_flag = 1;
} else if (strcmp(argv[1], "snap") == 0) {
struct aic_record_snapshot_info snap_info;
snprintf(recorder_cxt->capture_file_path, sizeof(recorder_cxt->capture_file_path),
"/sdcard/capture-%d.jpg", capture_count++);
snap_info.file_path = (s8 *)recorder_cxt->capture_file_path;
aic_recorder_snapshot(recorder_cxt->recorder, &snap_info);
} else if (strcmp(argv[1], "debug") == 0) {
aic_recorder_print_debug_info(recorder_cxt->recorder);
}
return ret;
}
MSH_CMD_EXPORT_ALIAS(recorder_demo_cmd, recorder_demo_cmd, recorder demo cmd);