以下是利用 libflv 库解析 FLV 文件大小和视频时间长度的 C 程序。/** * flv_info.c * 使用 libflv 库解析 FLV 文件获取文件大小和视频时长 * * 编译命令: * gcc -o flv_info flv_info.c -lflv -lpthread * * 交叉编译示例 (RV1106): * arm-rockchip830-linux-uclibcgnueabihf-gcc -o flv_info flv_info.c -I./include -L./lib -lflv -lpthread */ #include stdio.h #include stdlib.h #include string.h #include stdint.h #include sys/stat.h /* libflv 头文件 - 根据实际库的 API 调整 */ #include libflv/flv.h /* FLV Tag 类型定义 */ #define FLV_TAG_TYPE_AUDIO 0x08 #define FLV_TAG_TYPE_VIDEO 0x09 #define FLV_TAG_TYPE_SCRIPT 0x12 /* FLV 文件头大小 (9 bytes) */ #define FLV_HEADER_SIZE 9 /* Tag 头大小 (11 bytes) */ #define FLV_TAG_HEADER_SIZE 11 /* PreviousTagSize 字段大小 (4 bytes) */ #define PREV_TAG_SIZE_SIZE 4 /* 解析结果结构体 */ typedef struct { char filename[256]; /* 文件名 */ long file_size; /* 文件大小 (字节) */ double duration; /* 视频时长 (秒) */ uint32_t video_track; /* 是否有视频轨 (1有, 0无) */ uint32_t audio_track; /* 是否有音频轨 (1有, 0无) */ uint32_t video_codec; /* 视频编码类型 */ uint32_t audio_codec; /* 音频编码类型 */ uint32_t width; /* 视频宽度 (从 metadata 读取) */ uint32_t height; /* 视频高度 (从 metadata 读取) */ double framerate; /* 帧率 (从 metadata 读取) */ uint32_t sample_rate; /* 音频采样率 */ } FlvInfo; /* 获取文件大小 */ static long get_file_size(const char* filename) { struct stat st; if (stat(filename, st) ! 0) { return -1; } return st.st_size; } /* 解析 FLV 文件头验证文件格式并获取基本信息 */ static int parse_flv_header(FILE* fp, FlvInfo* info) { uint8_t header[FLV_HEADER_SIZE]; /* 读取 FLV 头 */ if (fread(header, 1, FLV_HEADER_SIZE, fp) ! FLV_HEADER_SIZE) { fprintf(stderr, Error: Failed to read FLV header\n); return -1; } /* 验证 FLV 签名 FLV */ if (header[0] ! F || header[1] ! L || header[2] ! V) { fprintf(stderr, Error: Not a valid FLV file (invalid signature)\n); return -1; } /* 版本号 */ uint8_t version header[3]; printf(FLV Version: %d\n, version); /* TypeFlagsReserved TypeFlagsAudio TypeFlagsVideo * header[4] 第4位: 视频存在标志, 第2位: 音频存在标志 */ uint8_t flags header[4]; info-video_track (flags 0x04) ? 1 : 0; info-audio_track (flags 0x01) ? 1 : 0; /* DataOffset: header 大小通常是 9 */ uint32_t data_offset (header[5] 16) | (header[6] 8) | header[7]; printf(Data offset: %d bytes\n, data_offset); printf(Video track: %s\n, info-video_track ? Yes : No); printf(Audio track: %s\n, info-audio_track ? Yes : No); /* 跳转到第一个 Tag 数据位置 */ if (data_offset FLV_HEADER_SIZE) { fseek(fp, data_offset - FLV_HEADER_SIZE, SEEK_CUR); } return 0; } /* 解析 FLV Tag 头获取 Tag 类型和数据大小 */ static int parse_tag_header(FILE* fp, uint8_t* tag_type, uint32_t* data_size, uint32_t* timestamp) { uint8_t header[FLV_TAG_HEADER_SIZE]; if (fread(header, 1, FLV_TAG_HEADER_SIZE, fp) ! FLV_TAG_HEADER_SIZE) { return -1; } /* Reserved Filter TagType (第1字节) */ *tag_type header[0]; /* DataSize: 24位大端整数 */ *data_size (header[1] 16) | (header[2] 8) | header[3]; /* Timestamp: 24位低8位 8位扩展位 32位 */ *timestamp (header[4] 16) | (header[5] 8) | header[6]; *timestamp | (header[7] 24); /* StreamID: 24位通常为0跳过 */ return 0; } /* 解析 onMetaData 脚本数据提取时长、宽高等信息 */ static int parse_metadata(const uint8_t* data, uint32_t size, FlvInfo* info) { /* * 注意: 这是一个简化版的 metadata 解析示例。 * 完整的 AMF (Action Message Format) 解析需要处理多种数据类型: * - AMF_TYPE_NUMBER (0x00): 8字节双精度浮点数 * - AMF_TYPE_BOOLEAN (0x01): 1字节布尔值 * - AMF_TYPE_STRING (0x02): 2字节长度 UTF-8字符串 * - AMF_TYPE_OBJECT (0x03): 键值对集合 * - AMF_TYPE_ECMA_ARRAY (0x08): 关联数组 * - AMF_TYPE_END (0x09): 对象结束标记 * * 实际使用中建议使用专门的 AMF 解析库。 */ printf( [Metadata] size: %u bytes\n, size); /* 简化处理: 在实际代码中这里应该完整解析 AMF 数据 * 读取 duration、width、height、framerate 等字段 */ return 0; } /* 解析视频 Tag 数据获取编码类型 */ static int parse_video_tag(const uint8_t* data, uint32_t size, FlvInfo* info) { if (size 1) return -1; /* FrameType (高4位) CodecID (低4位) */ uint8_t frame_and_codec data[0]; uint8_t frame_type (frame_and_codec 4) 0x0F; uint8_t codec_id frame_and_codec 0x0F; info-video_codec codec_id; /* CodecID 含义: * 2: Sorenson H.263 * 3: Screen video * 4: On2 VP6 * 5: On2 VP6 with alpha channel * 6: Screen video version 2 * 7: AVC (H.264) * 12: HEVC (H.265) */ const char* codec_name Unknown; switch (codec_id) { case 2: codec_name Sorenson H.263; break; case 3: codec_name Screen video; break; case 4: codec_name On2 VP6; break; case 5: codec_name On2 VP6 (alpha); break; case 6: codec_name Screen video v2; break; case 7: codec_name AVC (H.264); break; case 12: codec_name HEVC (H.265); break; } printf( Video Codec: %s (ID: %d)\n, codec_name, codec_id); printf( Frame Type: %s\n, frame_type 1 ? Keyframe : Interframe); /* 如果是 AVC/H.264可以进一步解析 AVCPacketType */ if (codec_id 7 size 2) { uint8_t avc_packet_type data[1]; const char* packet_type_desc ; switch (avc_packet_type) { case 0: packet_type_desc Sequence Header (SPS/PPS); break; case 1: packet_type_desc NALU; break; case 2: packet_type_desc Sequence End; break; } printf( AVC Packet Type: %s (%d)\n, packet_type_desc, avc_packet_type); } return 0; } /* 解析音频 Tag 数据获取编码类型和采样率 */ static int parse_audio_tag(const uint8_t* data, uint32_t size, FlvInfo* info) { if (size 1) return -1; /* SoundFormat (高4位) SoundRate (2位) SoundSize (1位) SoundType (1位) */ uint8_t sound_flags data[0]; uint8_t sound_format (sound_flags 4) 0x0F; uint8_t sound_rate (sound_flags 2) 0x03; uint8_t sound_size (sound_flags 1) 0x01; uint8_t sound_type sound_flags 0x01; info-audio_codec sound_format; /* SoundFormat 含义: * 0: Linear PCM (platform endian) * 1: ADPCM * 2: MP3 * 3: Linear PCM (little endian) * 4: Nellymoser 16kHz * 5: Nellymoser 8kHz * 6: Nellymoser * 7: G.711 A-law * 8: G.711 mu-law * 9: reserved * 10: AAC * 11: Speex * 14: MP3 8kHz * 15: Device-specific */ const char* codec_name Unknown; switch (sound_format) { case 0: codec_name Linear PCM; break; case 1: codec_name ADPCM; break; case 2: codec_name MP3; break; case 3: codec_name Linear PCM (LE); break; case 10: codec_name AAC; break; case 11: codec_name Speex; break; } /* 采样率 */ const uint32_t sample_rates[] {5512, 11025, 22050, 44100}; info-sample_rate sample_rates[sound_rate]; printf( Audio Codec: %s (ID: %d)\n, codec_name, sound_format); printf( Sample Rate: %d Hz\n, info-sample_rate); printf( Sound Size: %d-bit\n, sound_size ? 16 : 8); printf( Sound Type: %s\n, sound_type ? Stereo : Mono); /* 如果是 AAC可以进一步解析 AACPacketType */ if (sound_format 10 size 2) { uint8_t aac_packet_type data[1]; const char* packet_type_desc aac_packet_type 0 ? Sequence Header : Raw Data; printf( AAC Packet Type: %s (%d)\n, packet_type_desc, aac_packet_type); } return 0; } /* 主解析函数 */ int flv_parse(const char* filename, FlvInfo* info) { FILE* fp NULL; uint32_t max_timestamp 0; uint32_t tag_count 0; if (!filename || !info) { return -1; } /* 初始化 info 结构体 */ memset(info, 0, sizeof(FlvInfo)); strncpy(info-filename, filename, sizeof(info-filename) - 1); /* 获取文件大小 */ info-file_size get_file_size(filename); if (info-file_size 0) { fprintf(stderr, Error: Cannot get file size for %s\n, filename); return -1; } /* 打开文件 */ fp fopen(filename, rb); if (!fp) { fprintf(stderr, Error: Cannot open file %s\n, filename); return -1; } /* 解析 FLV 头 */ if (parse_flv_header(fp, info) 0) { fclose(fp); return -1; } printf(\n--- Parsing Tags ---\n); /* 遍历所有 Tag */ while (1) { uint8_t tag_type; uint32_t data_size; uint32_t timestamp; uint8_t* data NULL; long tag_start_pos; /* 读取 PreviousTagSize (4 bytes) */ uint32_t prev_tag_size; if (fread(prev_tag_size, 1, PREV_TAG_SIZE_SIZE, fp) ! PREV_TAG_SIZE_SIZE) { break; /* 文件结束 */ } /* 记录 Tag 起始位置 */ tag_start_pos ftell(fp); /* 解析 Tag 头 */ if (parse_tag_header(fp, tag_type, data_size, timestamp) 0) { break; } /* 检查数据有效性 */ if (data_size 0 || data_size 10 * 1024 * 1024) { /* 限制最大 10MB */ /* 跳过无效数据 */ fseek(fp, data_size, SEEK_CUR); continue; } /* 读取 Tag 数据 */ data (uint8_t*)malloc(data_size); if (!data) { break; } if (fread(data, 1, data_size, fp) ! data_size) { free(data); break; } /* 更新时间戳最大值 */ if (timestamp max_timestamp) { max_timestamp timestamp; } /* 根据 Tag 类型处理 */ printf(\nTag #%u:\n, tag_count); printf( Type: %s (0x%02X)\n, tag_type FLV_TAG_TYPE_AUDIO ? Audio : tag_type FLV_TAG_TYPE_VIDEO ? Video : tag_type FLV_TAG_TYPE_SCRIPT ? Script : Unknown, tag_type); printf( Data size: %u bytes\n, data_size); printf( Timestamp: %u ms (%.2f sec)\n, timestamp, timestamp / 1000.0); /* 根据类型解析具体数据 */ if (tag_type FLV_TAG_TYPE_VIDEO data_size 0) { parse_video_tag(data, data_size, info); } else if (tag_type FLV_TAG_TYPE_AUDIO data_size 0) { parse_audio_tag(data, data_size, info); } else if (tag_type FLV_TAG_TYPE_SCRIPT data_size 0) { parse_metadata(data, data_size, info); } free(data); } /* 计算视频时长 (最大时间戳差值) */ if (max_timestamp 0) { info-duration max_timestamp / 1000.0; } fclose(fp); return 0; } /* 打印解析结果 */ void print_flv_info(const FlvInfo* info) { printf(\n FLV File Information \n); printf(File: %s\n, info-filename); printf(File size: %ld bytes (%.2f KB, %.2f MB)\n, info-file_size, info-file_size / 1024.0, info-file_size / (1024.0 * 1024.0)); printf(Duration: %.2f seconds\n, info-duration); if (info-duration 0) { int hours (int)(info-duration / 3600); int minutes (int)((info-duration - hours * 3600) / 60); int seconds (int)(info-duration - hours * 3600 - minutes * 60); printf(Duration (H:M:S): %02d:%02d:%02d\n, hours, minutes, seconds); } printf(Video track: %s\n, info-video_track ? Yes : No); printf(Audio track: %s\n, info-audio_track ? Yes : No); if (info-video_track) { printf(Video codec ID: %u\n, info-video_codec); } if (info-audio_track) { printf(Audio codec ID: %u\n, info-audio_codec); printf(Audio sample rate: %u Hz\n, info-sample_rate); } printf(\n); } /* 主函数 */ int main(int argc, char* argv[]) { FlvInfo info; int ret; if (argc 2) { fprintf(stderr, Usage: %s flv_file\n, argv[0]); fprintf(stderr, Example: %s test.flv\n, argv[0]); return 1; } /* 初始化 libflv */ flv_init(); /* 解析 FLV 文件 */ ret flv_parse(argv[1], info); if (ret 0) { print_flv_info(info); } else { fprintf(stderr, Failed to parse FLV file\n); } /* 清理 libflv */ flv_deinit(); return ret; } 程序说明核心功能功能实现方式获取文件大小使用stat()系统调用获取视频时长遍历所有 Tag记录最大时间戳 (毫秒)解析编解码信息解析 Video/Audio Tag 中的编码标识FLV 文件结构解析textFLV 文件结构: ┌─────────────────┐ │ FLV Header │ 9 bytes: FLV version flags header_size ├─────────────────┤ │ PreviousTagSize │ 4 bytes: 第一个 Tag 前为 0 ├─────────────────┤ │ Tag #1 │ 11 bytes header data_size ├─────────────────┤ │ PreviousTagSize │ 4 bytes: Tag #1 大小 ├─────────────────┤ │ Tag #2 │ ├─────────────────┤ │ ... │ └─────────────────┘Tag 类型说明Tag 类型值说明Audio Tag0x08音频数据Video Tag0x09视频数据Script Tag0x12脚本/元数据 (onMetaData)视频时长计算原理FLV 文件中每个 Video/Audio Tag 都包含一个时间戳(单位毫秒)表示该帧相对于文件开始的时间。遍历所有 Tag取最大时间戳即为视频总时长。编译与运行bash# 编译 gcc -o flv_info flv_info.c -lflv # 运行 ./flv_info test.flv输出示例textFLV Version: 1 Data offset: 9 bytes Video track: Yes Audio track: Yes FLV File Information File: test.flv File size: 1048576 bytes (1024.00 KB, 1.00 MB) Duration: 30.12 seconds Duration (H:M:S): 00:00:30 Video track: Yes Audio track: Yes Video codec ID: 7 Audio codec ID: 10 Audio sample rate: 44100 Hz ⚠️ 注意事项API 适配本代码假设 libflv 提供flv_init()、flv_deinit()、flv_open()、flv_read_next_tag()等 API 。如果你的 libflv 版本 API 不同需要根据实际头文件调整。Metadata 解析onMetaData 采用 AMF 编码格式完整解析较为复杂 。本代码提供了简化版本实际使用建议使用专门的 AMF 解析库。大文件处理对于大文件遍历所有 Tag 可能耗时较长。如需快速获取时长可以优先解析 onMetaData 中的duration字段无需遍历全部 Tag。错误处理生产环境中应增加更完善的错误处理如文件损坏、格式异常等情况。方法二只读取duration字段/** * get_flv_duration.c * 功能: 快速从 FLV 文件的 onMetaData 中读取时长不遍历整个文件 * * 编译: gcc -o get_flv_duration get_flv_duration.c -lm * 运行: ./get_flv_duration test.flv */ #include stdio.h #include stdlib.h #include stdint.h #include string.h #include math.h // 从大端序的字节数组中读取双精度浮点数 (IEEE 754) double read_be_double(const uint8_t *data) { uint64_t bits 0; for (int i 0; i 8; i) { bits (bits 8) | data[i]; } double result; memcpy(result, bits, sizeof(result)); return result; } int main(int argc, char *argv[]) { if (argc 2) { fprintf(stderr, 用法: %s FLV文件路径\n, argv[0]); return 1; } const char *filename argv[1]; FILE *fp fopen(filename, rb); if (!fp) { perror(打开文件失败); return 1; } // 1. 跳过 FLV 文件头 (9 bytes) if (fseek(fp, 9, SEEK_SET) ! 0) { fprintf(stderr, 错误: 文件太小不是有效的FLV文件\n); fclose(fp); return 1; } // 2. 跳过第一个 PreviousTagSize 字段 (4 bytes) if (fseek(fp, 4, SEEK_CUR) ! 0) { fprintf(stderr, 错误: 无法跳过 PreviousTagSize\n); fclose(fp); return 1; } // 3. 读取第一个 Tag 的头部 uint8_t tag_header[11]; if (fread(tag_header, 1, 11, fp) ! 11) { fprintf(stderr, 错误: 无法读取第一个 Tag 的头部\n); fclose(fp); return 1; } // 检查 Tag 类型: 0x12 代表 Script Tag (onMetaData) if (tag_header[0] ! 0x12) { fprintf(stderr, 信息: 第一个 Tag 不是 onMetaData (类型: 0x%02X)\n, tag_header[0]); fclose(fp); return 1; } // 4. 解析 Tag 数据部分的大小 (24-bit big-endian) uint32_t data_size (tag_header[1] 16) | (tag_header[2] 8) | tag_header[3]; if (data_size 0 || data_size 1024 * 1024) { // 做个安全检查 fprintf(stderr, 错误: onMetaData 数据大小异常 (%u bytes)\n, data_size); fclose(fp); return 1; } // 5. 读取 onMetaData 的二进制数据 uint8_t *data (uint8_t *)malloc(data_size); if (!data) { fprintf(stderr, 内存分配失败\n); fclose(fp); return 1; } if (fread(data, 1, data_size, fp) ! data_size) { fprintf(stderr, 读取 onMetaData 数据失败\n); free(data); fclose(fp); return 1; } // 6. 在二进制数据中解析 AMF0 格式查找 duration 字段 // 数据格式: [String: onMetaData] [ECMA Array] ... int offset 0; // 第一个 AMF 项应该是 String (0x02) if (offset 1 data_size || data[offset] ! 0x02) { fprintf(stderr, 错误: 未找到 onMetaData 字符串标记\n); free(data); fclose(fp); return 1; } offset 1; // 跳过类型标记 // 读取字符串长度 (2 bytes, big-endian) uint16_t str_len (data[offset] 8) | data[offset 1]; offset 2; if (str_len ! 10 || memcmp(data[offset], onMetaData, 10) ! 0) { fprintf(stderr, 错误: 未找到 onMetaData 字符串\n); free(data); fclose(fp); return 1; } offset 10; // 跳过字符串内容 // 第二个 AMF 项应该是 ECMA Array (0x08) if (offset 1 data_size || data[offset] ! 0x08) { fprintf(stderr, 错误: 未找到 ECMA Array 标记\n); free(data); fclose(fp); return 1; } offset 1; // 跳过类型标记 // 跳过数组元素个数 (4 bytes) offset 4; // 遍历数组元素查找 duration double duration -1.0; while (offset 3 data_size) { // 读取元素名称的长度 (2 bytes, big-endian) uint16_t key_len (data[offset] 8) | data[offset 1]; offset 2; if (offset key_len 1 data_size) { break; // 数据不完整退出循环 } // 检查是否是 duration if (key_len 8 memcmp(data[offset], duration, 8) 0) { offset key_len; // 跳过键名 // 下一个字节是值的类型应该是 DOUBLE (0x00) if (offset 1 data_size || data[offset] ! 0x00) { fprintf(stderr, 警告: duration 的值类型不是 DOUBLE\n); break; } offset 1; // 跳过类型标记 // 读取 8 字节的 DOUBLE 值 if (offset 8 data_size) { fprintf(stderr, 错误: duration 数据不完整\n); break; } duration read_be_double(data[offset]); break; // 找到 duration退出循环 } else { // 不是 duration跳过这个键值对 offset key_len; // 跳过键名 if (offset 1 data_size) break; uint8_t val_type data[offset]; offset 1; // 跳过值的类型标记 // 根据不同类型跳过对应大小的值 switch (val_type) { case 0x00: // DOUBLE offset 8; break; case 0x01: // BOOLEAN offset 1; break; case 0x02: // STRING if (offset 2 data_size) break; uint16_t val_len (data[offset] 8) | data[offset 1]; offset 2 val_len; break; // 可以添加更多类型的处理但为了简洁其他类型简单跳过 default: // 对于复杂类型简单跳过是不安全的但对于查找 duration 足够了 // 这里为了安全直接退出循环 fprintf(stderr, 警告: 遇到未处理的数据类型 0x%02X停止解析\n, val_type); offset data_size; // 退出循环 break; } } } // 7. 输出结果 if (duration 0) { printf(文件: %s\n, filename); printf(视频时长: %.3f 秒\n, duration); // 也可以输出时、分、秒格式 int hours (int)(duration / 3600); int minutes (int)((duration - hours * 3600) / 60); int seconds (int)(duration - hours * 3600 - minutes * 60); printf( (H:M:S): %02d:%02d:%02d\n, hours, minutes, seconds); } else { fprintf(stderr, 错误: 未能在 onMetaData 中找到 duration 字段\n); } free(data); fclose(fp); return 0; }⚠️ 注意事项非所有 FLV 都有 onMetaData虽然这是主流编码器如FFmpeg的标准做法但某些 FLV 文件可能不包含这个 Tag。如果代码执行后未找到duration则只能退回到之前的遍历方案。duration 的精确性Adobe 官方文档曾指出onMetaData中的duration是一个近似值但通常与真实时长的误差非常小完全可以满足常规需求。字节序问题FLV 和 AMF 格式均采用网络字节序大端序Big-Endian而你的嵌入式设备可能是小端序Little-Endian。代码中的read_be_double函数就是为了处理这个转换。