Initial commit
This commit is contained in:
311
main/audio/demuxer/ogg_demuxer.cc
Normal file
311
main/audio/demuxer/ogg_demuxer.cc
Normal file
@@ -0,0 +1,311 @@
|
||||
#include "ogg_demuxer.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#define TAG "OggDemuxer"
|
||||
|
||||
/// @brief 重置解封器
|
||||
void OggDemuxer::Reset()
|
||||
{
|
||||
opus_info_ = {
|
||||
.head_seen = false,
|
||||
.tags_seen = false,
|
||||
.sample_rate = 48000
|
||||
};
|
||||
|
||||
state_ = ParseState::FIND_PAGE;
|
||||
ctx_.packet_len = 0;
|
||||
ctx_.seg_count = 0;
|
||||
ctx_.seg_index = 0;
|
||||
ctx_.data_offset = 0;
|
||||
ctx_.bytes_needed = 4; // 需要4字节"OggS"
|
||||
ctx_.seg_remaining = 0;
|
||||
ctx_.body_size = 0;
|
||||
ctx_.body_offset = 0;
|
||||
ctx_.packet_continued = false;
|
||||
|
||||
// 清空缓冲区数据
|
||||
memset(ctx_.header, 0, sizeof(ctx_.header));
|
||||
memset(ctx_.seg_table, 0, sizeof(ctx_.seg_table));
|
||||
memset(ctx_.packet_buf, 0, sizeof(ctx_.packet_buf));
|
||||
}
|
||||
|
||||
/// @brief 处理数据块
|
||||
/// @param data 输入数据
|
||||
/// @param size 输入数据大小
|
||||
/// @return 已处理的字节数
|
||||
size_t OggDemuxer::Process(const uint8_t* data, size_t size)
|
||||
{
|
||||
size_t processed = 0; // 已处理的字节数
|
||||
|
||||
while (processed < size) {
|
||||
switch (state_) {
|
||||
case ParseState::FIND_PAGE: {
|
||||
// 寻找页头"OggS"
|
||||
if (ctx_.bytes_needed < 4) {
|
||||
// 处理不完整的"OggS"匹配(跨数据块)
|
||||
size_t to_copy = std::min(size - processed, ctx_.bytes_needed);
|
||||
memcpy(ctx_.header + (4 - ctx_.bytes_needed), data + processed, to_copy);
|
||||
|
||||
processed += to_copy;
|
||||
ctx_.bytes_needed -= to_copy;
|
||||
|
||||
if (ctx_.bytes_needed == 0) {
|
||||
// 检查是否匹配"OggS"
|
||||
if (memcmp(ctx_.header, "OggS", 4) == 0) {
|
||||
state_ = ParseState::PARSE_HEADER;
|
||||
ctx_.data_offset = 4;
|
||||
ctx_.bytes_needed = 27 - 4; // 还需要23字节完成页头
|
||||
} else {
|
||||
// 匹配失败,滑动1字节继续匹配
|
||||
memmove(ctx_.header, ctx_.header + 1, 3);
|
||||
ctx_.bytes_needed = 1;
|
||||
}
|
||||
} else {
|
||||
// 数据不足,等待更多数据
|
||||
return processed;
|
||||
}
|
||||
} else if (ctx_.bytes_needed == 4) {
|
||||
// 在数据块中查找完整的"OggS"
|
||||
bool found = false;
|
||||
size_t i = 0;
|
||||
size_t remaining = size - processed;
|
||||
|
||||
// 搜索"OggS"
|
||||
for (; i + 4 <= remaining; i++) {
|
||||
if (memcmp(data + processed + i, "OggS", 4) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
// 找到"OggS",跳过已搜索的字节
|
||||
processed += i;
|
||||
|
||||
// 不记录找到的"OggS",无必要
|
||||
// memcpy(ctx_.header, data + processed, 4);
|
||||
processed += 4;
|
||||
|
||||
state_ = ParseState::PARSE_HEADER;
|
||||
ctx_.data_offset = 4;
|
||||
ctx_.bytes_needed = 27 - 4; // 还需要23字节
|
||||
} else {
|
||||
// 没有找到完整"OggS",保存可能的部分匹配
|
||||
size_t partial_len = remaining - i;
|
||||
if (partial_len > 0) {
|
||||
memcpy(ctx_.header, data + processed + i, partial_len);
|
||||
ctx_.bytes_needed = 4 - partial_len;
|
||||
processed += i + partial_len;
|
||||
} else {
|
||||
processed += i; // 已搜索所有字节
|
||||
}
|
||||
return processed; // 返回已处理的字节数
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "OggDemuxer run in error state: bytes_needed=%zu", ctx_.bytes_needed);
|
||||
Reset();
|
||||
return processed;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseState::PARSE_HEADER: {
|
||||
size_t available = size - processed;
|
||||
|
||||
if (available < ctx_.bytes_needed) {
|
||||
// 数据不足,复制可用的部分
|
||||
memcpy(ctx_.header + ctx_.data_offset,
|
||||
data + processed, available);
|
||||
|
||||
ctx_.data_offset += available;
|
||||
ctx_.bytes_needed -= available;
|
||||
processed += available;
|
||||
return processed; // 等待更多数据
|
||||
} else {
|
||||
// 有足够的数据完成页头
|
||||
size_t to_copy = ctx_.bytes_needed;
|
||||
memcpy(ctx_.header + ctx_.data_offset,
|
||||
data + processed, to_copy);
|
||||
|
||||
processed += to_copy;
|
||||
ctx_.data_offset += to_copy;
|
||||
ctx_.bytes_needed = 0;
|
||||
|
||||
// 验证页头
|
||||
if (ctx_.header[4] != 0) {
|
||||
ESP_LOGE(TAG, "无效的Ogg版本: %d", ctx_.header[4]);
|
||||
state_ = ParseState::FIND_PAGE;
|
||||
ctx_.bytes_needed = 4;
|
||||
ctx_.data_offset = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
ctx_.seg_count = ctx_.header[26];
|
||||
if (ctx_.seg_count > 0 && ctx_.seg_count <= 255) {
|
||||
state_ = ParseState::PARSE_SEGMENTS;
|
||||
ctx_.bytes_needed = ctx_.seg_count;
|
||||
ctx_.data_offset = 0;
|
||||
} else if (ctx_.seg_count == 0) {
|
||||
// 没有段,直接跳到下一个页面
|
||||
state_ = ParseState::FIND_PAGE;
|
||||
ctx_.bytes_needed = 4;
|
||||
ctx_.data_offset = 0;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "无效的段数: %u", ctx_.seg_count);
|
||||
state_ = ParseState::FIND_PAGE;
|
||||
ctx_.bytes_needed = 4;
|
||||
ctx_.data_offset = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseState::PARSE_SEGMENTS: {
|
||||
size_t available = size - processed;
|
||||
|
||||
if (available < ctx_.bytes_needed) {
|
||||
memcpy(ctx_.seg_table + ctx_.data_offset,
|
||||
data + processed, available);
|
||||
|
||||
ctx_.data_offset += available;
|
||||
ctx_.bytes_needed -= available;
|
||||
processed += available;
|
||||
return processed; // 等待更多数据
|
||||
} else {
|
||||
size_t to_copy = ctx_.bytes_needed;
|
||||
memcpy(ctx_.seg_table + ctx_.data_offset,
|
||||
data + processed, to_copy);
|
||||
|
||||
processed += to_copy;
|
||||
ctx_.data_offset += to_copy;
|
||||
ctx_.bytes_needed = 0;
|
||||
|
||||
state_ = ParseState::PARSE_DATA;
|
||||
ctx_.seg_index = 0;
|
||||
ctx_.data_offset = 0;
|
||||
|
||||
// 计算数据体总大小
|
||||
ctx_.body_size = 0;
|
||||
for (size_t i = 0; i < ctx_.seg_count; ++i) {
|
||||
ctx_.body_size += ctx_.seg_table[i];
|
||||
}
|
||||
ctx_.body_offset = 0;
|
||||
ctx_.seg_remaining = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseState::PARSE_DATA: {
|
||||
while (ctx_.seg_index < ctx_.seg_count && processed < size) {
|
||||
uint8_t seg_len = ctx_.seg_table[ctx_.seg_index];
|
||||
|
||||
// 检查段数据是否已经部分读取
|
||||
if (ctx_.seg_remaining > 0) {
|
||||
seg_len = ctx_.seg_remaining;
|
||||
} else {
|
||||
ctx_.seg_remaining = seg_len;
|
||||
}
|
||||
|
||||
// 检查缓冲区是否足够
|
||||
if (ctx_.packet_len + seg_len > sizeof(ctx_.packet_buf)) {
|
||||
ESP_LOGE(TAG, "包缓冲区溢出: %zu + %u > %zu", ctx_.packet_len, seg_len, sizeof(ctx_.packet_buf));
|
||||
state_ = ParseState::FIND_PAGE;
|
||||
ctx_.packet_len = 0;
|
||||
ctx_.packet_continued = false;
|
||||
ctx_.seg_remaining = 0;
|
||||
ctx_.bytes_needed = 4;
|
||||
return processed;
|
||||
}
|
||||
|
||||
// 复制数据
|
||||
size_t to_copy = std::min(size - processed, (size_t)seg_len);
|
||||
memcpy(ctx_.packet_buf + ctx_.packet_len, data + processed, to_copy);
|
||||
|
||||
processed += to_copy;
|
||||
ctx_.packet_len += to_copy;
|
||||
ctx_.body_offset += to_copy;
|
||||
ctx_.seg_remaining -= to_copy;
|
||||
|
||||
// 检查段是否完整
|
||||
if (ctx_.seg_remaining > 0) {
|
||||
// 段不完整,等待更多数据
|
||||
return processed;
|
||||
}
|
||||
|
||||
// 段完整
|
||||
bool seg_continued = (ctx_.seg_table[ctx_.seg_index] == 255);
|
||||
|
||||
if (!seg_continued) {
|
||||
// 包结束
|
||||
if (ctx_.packet_len) {
|
||||
if (!opus_info_.head_seen) {
|
||||
if (ctx_.packet_len >=8 && memcmp(ctx_.packet_buf, "OpusHead", 8) == 0) {
|
||||
opus_info_.head_seen = true;
|
||||
if (ctx_.packet_len >= 19) {
|
||||
opus_info_.sample_rate = ctx_.packet_buf[12] |
|
||||
(ctx_.packet_buf[13] << 8) |
|
||||
(ctx_.packet_buf[14] << 16) |
|
||||
(ctx_.packet_buf[15] << 24);
|
||||
ESP_LOGD(TAG, "OpusHead found, sample_rate=%d", opus_info_.sample_rate);
|
||||
}
|
||||
ctx_.packet_len = 0;
|
||||
ctx_.packet_continued = false;
|
||||
ctx_.seg_index++;
|
||||
ctx_.seg_remaining = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!opus_info_.tags_seen) {
|
||||
if (ctx_.packet_len >= 8 && memcmp(ctx_.packet_buf, "OpusTags", 8) == 0) {
|
||||
opus_info_.tags_seen = true;
|
||||
ESP_LOGD(TAG, "OpusTags found.");
|
||||
ctx_.packet_len = 0;
|
||||
ctx_.packet_continued = false;
|
||||
ctx_.seg_index++;
|
||||
ctx_.seg_remaining = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (opus_info_.head_seen && opus_info_.tags_seen) {
|
||||
if (on_demuxer_finished_) {
|
||||
on_demuxer_finished_(ctx_.packet_buf, opus_info_.sample_rate, ctx_.packet_len);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "当前Ogg容器未解析到OpusHead/OpusTags,丢弃");
|
||||
}
|
||||
}
|
||||
ctx_.packet_len = 0;
|
||||
ctx_.packet_continued = false;
|
||||
} else {
|
||||
ctx_.packet_continued = true;
|
||||
}
|
||||
|
||||
ctx_.seg_index++;
|
||||
ctx_.seg_remaining = 0;
|
||||
}
|
||||
|
||||
if (ctx_.seg_index == ctx_.seg_count) {
|
||||
// 检查是否所有数据体都已读取
|
||||
if (ctx_.body_offset < ctx_.body_size) {
|
||||
ESP_LOGW(TAG, "数据体不完整: %zu/%zu",
|
||||
ctx_.body_offset, ctx_.body_size);
|
||||
}
|
||||
|
||||
// 如果包跨页,保持packet_len和packet_continued
|
||||
if (!ctx_.packet_continued) {
|
||||
ctx_.packet_len = 0;
|
||||
}
|
||||
|
||||
// 进入下一页面
|
||||
state_ = ParseState::FIND_PAGE;
|
||||
ctx_.bytes_needed = 4;
|
||||
ctx_.data_offset = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
63
main/audio/demuxer/ogg_demuxer.h
Normal file
63
main/audio/demuxer/ogg_demuxer.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef OGG_DEMUXER_H_
|
||||
#define OGG_DEMUXER_H_
|
||||
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
class OggDemuxer {
|
||||
private:
|
||||
enum ParseState : int8_t {
|
||||
FIND_PAGE,
|
||||
PARSE_HEADER,
|
||||
PARSE_SEGMENTS,
|
||||
PARSE_DATA
|
||||
};
|
||||
|
||||
struct Opus_t {
|
||||
bool head_seen{false};
|
||||
bool tags_seen{false};
|
||||
int sample_rate{48000};
|
||||
};
|
||||
|
||||
|
||||
// 使用固定大小的缓冲区避免动态分配
|
||||
struct context_t {
|
||||
bool packet_continued{false}; // 当前包是否跨多个段
|
||||
uint8_t header[27]; // Ogg页头
|
||||
uint8_t seg_table[255]; // 当前存储的段表
|
||||
uint8_t packet_buf[8192]; // 8KB包缓冲区
|
||||
size_t packet_len = 0; // 缓冲区中累计的数据长度
|
||||
size_t seg_count = 0; // 当前页段数
|
||||
size_t seg_index = 0; // 当前处理的段索引
|
||||
size_t data_offset = 0; // 解析当前阶段已读取的字节数
|
||||
size_t bytes_needed = 0; // 解析当前字段还需要读取的字节数
|
||||
size_t seg_remaining = 0; // 当前段剩余需要读取的字节数
|
||||
size_t body_size = 0; // 数据体总大小
|
||||
size_t body_offset = 0; // 数据体已读取的字节数
|
||||
};
|
||||
|
||||
public:
|
||||
OggDemuxer() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
void Reset();
|
||||
|
||||
size_t Process(const uint8_t* data, size_t size);
|
||||
|
||||
/// @brief 设置解封装完毕后回调处理函数
|
||||
/// @param on_demuxer_finished
|
||||
void OnDemuxerFinished(std::function<void(const uint8_t* data, int sample_rate, size_t len)> on_demuxer_finished) {
|
||||
on_demuxer_finished_ = on_demuxer_finished;
|
||||
}
|
||||
private:
|
||||
|
||||
ParseState state_ = ParseState::FIND_PAGE;
|
||||
context_t ctx_;
|
||||
Opus_t opus_info_;
|
||||
std::function<void(const uint8_t*, int, size_t)> on_demuxer_finished_;
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user