news 2026/4/18 7:06:13

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
利用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 Tag0x08音频数据
Video Tag0x09视频数据
Script Tag0x12脚本/元数据 (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函数就是为了处理这个转换。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 6:59:58

StructBERT模型压力测试与性能调优指南

StructBERT模型压力测试与性能调优指南 你是不是已经成功部署了StructBERT模型服务&#xff0c;感觉一切运行正常&#xff0c;但心里总有点没底&#xff1f;当用户量突然上来&#xff0c;或者需要处理大批量文本时&#xff0c;它还能扛得住吗&#xff1f;会不会突然变慢甚至崩…

作者头像 李华
网站建设 2026/4/18 6:59:07

S2-Pro命令行工具开发:使用Node.js构建模型管理CLI

S2-Pro命令行工具开发&#xff1a;使用Node.js构建模型管理CLI 1. 为什么开发者需要一个模型管理CLI 在AI模型开发和部署过程中&#xff0c;频繁登录网页控制台进行模型管理既低效又影响工作流。想象一下这样的场景&#xff1a;你正在本地调试代码&#xff0c;突然需要重启云…

作者头像 李华
网站建设 2026/4/18 6:56:16

HarmonyOS应用与游戏开发:技术深度解析与面试指南

引言 HarmonyOS(鸿蒙操作系统)是华为推出的分布式操作系统,旨在为多设备提供无缝体验。随着“HarmonyOS APP或游戏”、“HarmonyOS PC”等主题的兴起,开发者需掌握原生应用开发、Android整合、嵌入式技术等技能。本文基于职位描述(如海思平台安卓鸿蒙工程师和Android鸿蒙…

作者头像 李华
网站建设 2026/4/18 6:53:41

告别手动复制!用Arduino IDE插件一键上传网页文件到ESP32 SPIFFS

告别手动复制&#xff01;用Arduino IDE插件一键上传网页文件到ESP32 SPIFFS 每次为ESP32 Web服务器项目更新前端文件时&#xff0c;你是否还在重复这些操作&#xff1a;手动压缩文件→通过串口工具上传→重启设备→检查文件完整性&#xff1f;这种低效的工作流会让前端开发变得…

作者头像 李华
网站建设 2026/4/18 6:52:00

藏在Ld/Lq参数里的秘密:永磁同步电机电感参数测量避坑指南

藏在Ld/Lq参数里的秘密&#xff1a;永磁同步电机电感参数测量避坑指南 永磁同步电机&#xff08;PMSM&#xff09;作为高效能电机代表&#xff0c;其控制性能与电感参数Ld、Lq的测量精度直接相关。然而在实际工程中&#xff0c;超过60%的现场调试问题源于电感参数测量误差。本文…

作者头像 李华