利用libflv库录制成flv视频文件
libflv 是一个支持 H264/H265 和 AAC 的 FLV 封装库。下面是一个完整的示例程序演示如何将 H.264 和 AAC 裸流封装成 FLV 文件。/** * flv_recorder.c * 使用 libflv 库将 H264 和 AAC 流录制为 FLV 文件 * * 编译命令: * gcc -o flv_recorder flv_recorder.c -lflv -lpthread * * 交叉编译示例 (RV1106): * arm-rockchip830-linux-uclibcgnueabihf-gcc -o flv_recorder flv_recorder.c -I./include -L./lib -lflv -lpthread */ #include stdio.h #include stdlib.h #include string.h #include stdint.h #include unistd.h #include sys/time.h /* 假设 libflv 的头文件结构请根据实际库调整 */ #include flv.h /* 文件输出上下文 */ typedef struct { FILE* fp; /* FLV 文件句柄 */ uint32_t video_pts; /* 视频时间戳 (毫秒) */ uint32_t audio_pts; /* 音频时间戳 (毫秒) */ uint64_t start_time;/* 起始时间戳 (微秒) */ } FlvMuxerContext; /* 获取当前时间戳 (毫秒) */ static uint32_t get_current_timestamp_ms(void) { struct timeval tv; gettimeofday(tv, NULL); return (uint32_t)(tv.tv_sec * 1000 tv.tv_usec / 1000); } /* 获取相对时间戳 (从起始时间开始) */ static uint32_t get_relative_timestamp(FlvMuxerContext* ctx) { struct timeval tv; gettimeofday(tv, NULL); uint64_t now_us tv.tv_sec * 1000000 tv.tv_usec; return (uint32_t)((now_us - ctx-start_time) / 1000); } /* 写入回调函数 - 供 libflv 调用 */ static int flv_write_callback(void* user_data, const uint8_t* data, int size) { FlvMuxerContext* ctx (FlvMuxerContext*)user_data; if (!ctx || !ctx-fp) return -1; size_t written fwrite(data, 1, size, ctx-fp); return (written size) ? 0 : -1; } /** * 初始化 FLV 封装器 * param filename 输出文件名 * param video_width 视频宽度 (用于 metadata) * param video_height 视频高度 (用于 metadata) * return FlvMuxerContext* 成功返回上下文失败返回 NULL */ FlvMuxerContext* flv_muxer_init(const char* filename, int video_width, int video_height) { FlvMuxerContext* ctx (FlvMuxerContext*)calloc(1, sizeof(FlvMuxerContext)); if (!ctx) return NULL; ctx-fp fopen(filename, wb); if (!ctx-fp) { free(ctx); return NULL; } /* 调用 libflv 初始化 */ flv_init(); /* 写入 FLV 头 */ if (flv_write_header(ctx-fp) 0) { fclose(ctx-fp); free(ctx); return NULL; } /* 写入 onMetaData (可选增强播放器兼容性) */ /* 注意: 具体 API 请参考 libflv 的实际接口 */ // flv_write_metadata(ctx-fp, video_width, video_height); ctx-start_time get_current_timestamp_ms() * 1000; printf(FLV muxer initialized: %s\n, filename); return ctx; } /** * 写入视频同步包 (AVC Sequence Header - SPS/PPS) * param ctx FLV 上下文 * param sps SPS 数据 * param sps_size SPS 大小 * param pps PPS 数据 * param pps_size PPS 大小 * return 0 成功-1 失败 */ int flv_write_video_sps_pps(FlvMuxerContext* ctx, const uint8_t* sps, int sps_size, const uint8_t* pps, int pps_size) { if (!ctx || !sps || !pps) return -1; /* 构造 AVCDecoderConfigurationRecord */ uint8_t* avc_config NULL; int config_size 0; /* * AVCDecoderConfigurationRecord 结构: * configurationVersion (1 byte): 0x01 * AVCProfileIndication (1 byte): 从 SPS 获取 * profile_compatibility (1 byte): 从 SPS 获取 * AVCLevelIndication (1 byte): 从 SPS 获取 * lengthSizeMinusOne (1 byte): 0xFF (4 字节 NALU 长度) * numOfSequenceParameterSets (1 byte): 0xE1 (1 个 SPS) * sequenceParameterSetLength (2 bytes): SPS 长度 * sequenceParameterSetNALUnit (N bytes): SPS 数据 * numOfPictureParameterSets (1 byte): 0x01 (1 个 PPS) * pictureParameterSetLength (2 bytes): PPS 长度 * pictureParameterSetNALUnit (N bytes): PPS 数据 */ config_size 1 1 1 1 1 1 2 sps_size 1 2 pps_size; avc_config (uint8_t*)malloc(config_size); if (!avc_config) return -1; uint8_t* ptr avc_config; *ptr 0x01; /* configurationVersion */ *ptr sps[1]; /* AVCProfileIndication */ *ptr sps[2]; /* profile_compatibility */ *ptr sps[3]; /* AVCLevelIndication */ *ptr 0xFF; /* lengthSizeMinusOne */ *ptr 0xE1; /* numOfSequenceParameterSets */ *ptr (sps_size 8) 0xFF; /* sequenceParameterSetLength (high) */ *ptr sps_size 0xFF; /* sequenceParameterSetLength (low) */ memcpy(ptr, sps, sps_size); /* SPS NALU */ ptr sps_size; *ptr 0x01; /* numOfPictureParameterSets */ *ptr (pps_size 8) 0xFF; /* pictureParameterSetLength (high) */ *ptr pps_size 0xFF; /* pictureParameterSetLength (low) */ memcpy(ptr, pps, pps_size); /* PPS NALU */ /* * 写入视频 Tag * FrameType 1 (关键帧), CodecID 7 (AVC) * AVCPacketType 0 (序列头) * CompositionTime 0 */ // flv_write_video_tag(ctx-fp, 1, 7, 0, 0, avc_config, config_size); free(avc_config); printf(Video SPS/PPS written\n); return 0; } /** * 写入视频帧 (H264 NALU) * param ctx FLV 上下文 * param data H264 NALU 数据 (不含起始码 0x00 0x00 0x00 0x01) * param size 数据大小 * param is_keyframe 是否为关键帧 (1: IDR帧, 0: P/B帧) * return 0 成功-1 失败 */ int flv_write_video_frame(FlvMuxerContext* ctx, const uint8_t* data, int size, int is_keyframe) { if (!ctx || !data || size 0) return -1; uint32_t pts get_relative_timestamp(ctx); ctx-video_pts pts; /* * FrameType: 1 关键帧, 2 非关键帧 * CodecID: 7 AVC (H264) * AVCPacketType: 1 NALU 单元 * CompositionTime: 有 B 帧时使用无 B 帧时为 0 */ int frame_type is_keyframe ? 1 : 2; /* 在 NALU 前添加 4 字节长度前缀 (大端序) */ uint8_t* nal_with_len (uint8_t*)malloc(size 4); if (!nal_with_len) return -1; nal_with_len[0] (size 24) 0xFF; nal_with_len[1] (size 16) 0xFF; nal_with_len[2] (size 8) 0xFF; nal_with_len[3] size 0xFF; memcpy(nal_with_len 4, data, size); // flv_write_video_tag(ctx-fp, frame_type, 7, 1, 0, nal_with_len, size 4); free(nal_with_len); return 0; } /** * 写入音频同步包 (Audio Specific Config - AAC 配置) * param ctx FLV 上下文 * param audio_specific_config AudioSpecificConfig (2 字节) * param config_size 配置大小 (通常为 2) * return 0 成功-1 失败 */ int flv_write_audio_specific_config(FlvMuxerContext* ctx, const uint8_t* audio_specific_config, int config_size) { if (!ctx || !audio_specific_config) return -1; /* * SoundFormat: 10 AAC * SoundRate: 3 44kHz, 2 22kHz, 1 11kHz, 0 5.5kHz * SoundSize: 1 16-bit samples * SoundType: 1 Stereo sound, 0 Mono * AACPacketType: 0 序列头 */ uint8_t sound_format 0x10; /* AAC, 44kHz, 16-bit, Stereo */ // flv_write_audio_tag(ctx-fp, sound_format, 0, audio_specific_config, config_size); printf(Audio Specific Config written\n); return 0; } /** * 写入音频帧 (AAC 原始数据不含 ADTS 头) * param ctx FLV 上下文 * param data AAC 原始数据 (不含 ADTS 头的 Raw AAC) * param size 数据大小 * return 0 成功-1 失败 */ int flv_write_audio_frame(FlvMuxerContext* ctx, const uint8_t* data, int size) { if (!ctx || !data || size 0) return -1; uint32_t pts get_relative_timestamp(ctx); ctx-audio_pts pts; /* * SoundFormat: 10 AAC * AACPacketType: 1 原始数据 */ uint8_t sound_format 0x11; /* AAC, 44kHz, 16-bit, Stereo, AACPacketType1 */ // flv_write_audio_tag(ctx-fp, sound_format, 1, data, size); return 0; } /** * 完成 FLV 文件写入并清理资源 * param ctx FLV 上下文 */ void flv_muxer_close(FlvMuxerContext* ctx) { if (!ctx) return; /* 写入 FLV 文件尾 */ // flv_write_trailer(ctx-fp); if (ctx-fp) { fclose(ctx-fp); } flv_deinit(); free(ctx); printf(FLV recording finished\n); } /* 示例模拟数据录制 */ /* 示例: 从文件读取 H264 NALU */ int read_h264_nalu(FILE* fp, uint8_t* buffer, int* size, int* is_keyframe) { /* 简化示例实际应用中需要解析 H264 NALU */ /* 需要跳过起始码 (0x00 0x00 0x00 0x01 或 0x00 0x00 0x01) */ /* 并通过 NALU 类型判断是否为关键帧 (NALU type 5 为 IDR 帧) */ return 0; } /* 示例: 从文件读取 AAC 帧 */ int read_aac_frame(FILE* fp, uint8_t* buffer, int* size) { /* 简化示例实际应用中需要去除 ADTS 头 (前 7 或 9 字节) */ return 0; } int main(int argc, char* argv[]) { FlvMuxerContext* ctx NULL; /* 初始化 FLV 封装器 */ ctx flv_muxer_init(test.flv, 1920, 1080); if (!ctx) { fprintf(stderr, Failed to initialize FLV muxer\n); return -1; } /* 写入同步包 (必须最先发送) */ /* 1. 视频同步包 (SPS/PPS) - 实际应用中需要从编码器获取 */ /* uint8_t sps[] {0x67, 0x64, 0x00, 0x1F, ...}; uint8_t pps[] {0x68, 0xEE, 0x3C, 0x80, ...}; flv_write_video_sps_pps(ctx, sps, sizeof(sps), pps, sizeof(pps)); */ /* 2. 音频同步包 (Audio Specific Config) - 实际应用中需要从编码器获取 */ /* uint8_t audio_config[] {0x12, 0x10}; /* AAC LC, 44.1kHz, Stereo */ flv_write_audio_specific_config(ctx, audio_config, sizeof(audio_config)); */ /* 写入音视频帧 */ /* 示例: 循环写入帧数据 */ /* for (int i 0; i 100; i) { uint8_t frame_data[65536]; int frame_size; if (read_h264_nalu(h264_fp, frame_data, frame_size, is_key)) { flv_write_video_frame(ctx, frame_data, frame_size, is_key); } if (read_aac_frame(aac_fp, frame_data, frame_size)) { flv_write_audio_frame(ctx, frame_data, frame_size); } usleep(33000); // 约 30fps } */ /* 完成录制 */ flv_muxer_close(ctx); printf(FLV file generated: test.flv\n); return 0; }API 使用说明根据 FLV 格式标准写入流程必须遵循以下顺序1. 初始化顺序textFLV Header → Metadata → Video Sequence Header → Audio Sequence Header → 音视频数据 → FLV Trailer2. Video Tag 结构字段大小说明FrameType CodecID1 byte高4位帧类型(1关键帧,2非关键帧)低4位编码ID(7AVC)AVCPacketType1 byte0序列头(SPS/PPS), 1NALU单元, 2序列结束CompositionTime3 bytesPTS与DTS的偏移(毫秒)无B帧时为0DataN bytes视频数据3. Audio Tag 结构字段大小说明SoundFormat Rate Size Type1 byte高4位编码格式(10AAC)低4位采样率/位深/声道AACPacketType1 byte0序列头(AudioSpecificConfig), 1原始AAC帧DataN bytes音频数据(不含ADTS头)⚠️ 注意事项SPS/PPS 提取从 H264 编码器获取关键帧(IDR)前必须发送AAC ADTS 头处理写入前需去除 7 或 9 字节的 ADTS 头时间戳单位毫秒建议使用相对时间戳(从第一帧开始)NALU 长度前缀FLV 中使用 4 字节大端序长度而非起始码注意以上代码中的flv_write_xxx函数需要根据你实际使用的 libflv 版本 API 进行替换。建议先查看库提供的头文件了解具体的函数接口。