利用flv库读取flv文件时长c程序

以下是利用 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 文件结构解析

text

复制代码
FLV 文件结构:
┌─────────────────┐
│   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 Tag 0x08 音频数据
Video Tag 0x09 视频数据
Script Tag 0x12 脚本/元数据 (onMetaData)

视频时长计算原理

FLV 文件中每个 Video/Audio Tag 都包含一个 时间戳 (单位:毫秒),表示该帧相对于文件开始的时间。遍历所有 Tag,取最大时间戳即为视频总时长。

编译与运行

bash

复制代码
# 编译
gcc -o flv_info flv_info.c -lflv

# 运行
./flv_info test.flv

输出示例

text

复制代码
FLV 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
==========================================

⚠️ 注意事项

  1. API 适配 :本代码假设 libflv 提供 flv_init()flv_deinit()flv_open()flv_read_next_tag() 等 API 。如果你的 libflv 版本 API 不同,需要根据实际头文件调整。

  2. Metadata 解析:onMetaData 采用 AMF 编码格式,完整解析较为复杂 。本代码提供了简化版本,实际使用建议使用专门的 AMF 解析库。

  3. 大文件处理 :对于大文件,遍历所有 Tag 可能耗时较长。如需快速获取时长,可以优先解析 onMetaData 中的 duration 字段,无需遍历全部 Tag。

  4. 错误处理:生产环境中应增加更完善的错误处理,如文件损坏、格式异常等情况。

方法二,只读取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;
}

⚠️ 注意事项

  1. 非所有 FLV 都有 onMetaData :虽然这是主流编码器(如FFmpeg)的标准做法,但某些 FLV 文件可能不包含这个 Tag。如果代码执行后未找到 duration,则只能退回到之前的遍历方案。

  2. duration 的精确性 :Adobe 官方文档曾指出,onMetaData 中的 duration 是一个近似值,但通常与真实时长的误差非常小,完全可以满足常规需求。

  3. 字节序问题 :FLV 和 AMF 格式均采用网络字节序(大端序,Big-Endian) ,而你的嵌入式设备可能是小端序(Little-Endian)。代码中的 read_be_double 函数就是为了处理这个转换。

相关推荐
NotFound4862 小时前
Go语言中的图形界面开发实战解析:从GUI到WebAssembly
开发语言·golang·wasm
Rust研习社2 小时前
Rust Default 特征详解:轻松实现类型默认值
开发语言·后端·rust
jiayong232 小时前
第 25 课:给学习笔记页加上搜索、标签筛选和 URL 同步
开发语言·前端·javascript·vue.js·学习
浅时光_c2 小时前
12 函数
c语言
小文数模2 小时前
2026 年MathorCup(妈妈杯)数学建模竞赛C完整参考论文(第一版)
c语言·数学建模·matlab
想唱rap2 小时前
C++11之包装器
服务器·开发语言·c++·算法·ubuntu
汽车芯猿2 小时前
嵌入式 SHA-256 完全实现(附原码)(无 uint64_t,减少栈使用)
c语言·单片机
wuminyu2 小时前
专家视角看Java的线程是如何run起来的过程
java·linux·c语言·jvm·c++