Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion include/cast_protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,30 @@ typedef enum {
* @param data 送信するデータのポインタ
* @param len データ長
* @param fmt 画像のフォーマット
* @param width 画像の幅(px)。JPEGでは0可
* @param height 画像の高さ(px)。JPEGでは0可
* @param transport 無線規格の振る舞い
* @return esp_err_t 送信成否(ESP_OK, ESP_FAILなど)
*/
esp_err_t cast_send_frame(
const uint8_t *data,
size_t len,
cast_image_format_t fmt,
uint16_t width,
uint16_t height,
cast_transport_interface_t *transport
);

/**
* @brief 画像が完成したときに呼ばれる関数の型
* @note dataはcallbackから戻った後にミドルウェアが解放する。保持する場合はコピーすること。
* @param data 復元された画像データ
* @param len データ長
* @param fmt 画像フォーマット
* @param width 画像の幅(px)
* @param height 画像の高さ(px)
*/
typedef void (*cast_on_frame_ready_t)(const uint8_t *data, size_t len, cast_image_format_t fmt);
typedef void (*cast_on_frame_ready_t)(const uint8_t *data, size_t len, cast_image_format_t fmt, uint16_t width, uint16_t height);

/**
* @brief 受信側の初期化
Expand Down
4 changes: 4 additions & 0 deletions src/cast_chunker.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ esp_err_t cast_send_frame(
const uint8_t *data,
size_t len,
cast_image_format_t fmt,
uint16_t width,
uint16_t height,
cast_transport_interface_t *transport
) {
static uint16_t frame_counter = 0;
Expand Down Expand Up @@ -39,6 +41,8 @@ esp_err_t cast_send_frame(
header->chunk_index = i;
header->total_chunks = total_chunks;
header->max_payload = (uint16_t)max_payload;
header->width = width;
header->height = height;
header->payload_len = (uint16_t)current_payload_len;

// 2. データのコピー(ヘッダの直後へ)
Expand Down
2 changes: 2 additions & 0 deletions src/cast_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ typedef struct {
uint16_t chunk_index; // チャンク番号、または応答対象のチャンク番号
uint16_t total_chunks; // 総チャンク数、または制御用パラメータ
uint16_t max_payload; // 送信側が分割に使用した1チャンクの最大サイズ
uint16_t width; // 画像の幅(px)。JPEGでは0可
uint16_t height; // 画像の高さ(px)。JPEGでは0可
uint16_t payload_len; // このパケットに含まれるデータ長(DATA以外では通常0)
/* @note この後に最大MTU-2のサイズとなるまで実際のデータが入る */
} __attribute__((packed)) cast_header_t;
Expand Down
6 changes: 5 additions & 1 deletion src/cast_reassembler.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ typedef struct {
uint16_t max_payload_ref; // オフセット計算の基準値
uint16_t last_chunk_len; // 最終チャンクのペイロード長
cast_image_format_t format; // 画像フォーマット
uint16_t width; // 画像の幅(px)
uint16_t height; // 画像の高さ(px)
bool is_active;
} cast_reassembler_ctx_t;

Expand Down Expand Up @@ -76,6 +78,8 @@ static void cast_reassembler_push_packet(const uint8_t *data, size_t len) {
ctx.total_chunks = header->total_chunks;
ctx.max_payload_ref = header->max_payload;
ctx.format = (cast_image_format_t)header->format;
ctx.width = header->width;
ctx.height = header->height;
ctx.chunks_received = 0;
ctx.is_active = true;
}
Expand All @@ -97,7 +101,7 @@ static void cast_reassembler_push_packet(const uint8_t *data, size_t len) {
if (ctx.chunks_received == ctx.total_chunks) {
if (app_callback) {
size_t final_size = (ctx.total_chunks - 1) * ctx.max_payload_ref + ctx.last_chunk_len;
app_callback(ctx.buffer, final_size, ctx.format);
app_callback(ctx.buffer, final_size, ctx.format, ctx.width, ctx.height);
}

// callback から戻ったらミドルウェア側で解放
Expand Down
34 changes: 21 additions & 13 deletions test/host/test_cast_chunker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ TEST_F(ChunkerTest, ExactFitChunkCount)
const size_t data_len = max_payload * 2;
std::vector<uint8_t> data(data_len, 0xAA);

esp_err_t ret = cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
esp_err_t ret = cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);

EXPECT_EQ(ret, ESP_OK);
EXPECT_EQ(g_captured_packets.size(), 2u);
Expand All @@ -108,7 +108,7 @@ TEST_F(ChunkerTest, PartialLastChunkCount)
// max_payload + 1 バイトで確実に2チャンク
std::vector<uint8_t> data(max_payload + 1, 0xBB);

esp_err_t ret = cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
esp_err_t ret = cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);

EXPECT_EQ(ret, ESP_OK);
EXPECT_EQ(g_captured_packets.size(), 2u);
Expand All @@ -119,7 +119,7 @@ TEST_F(ChunkerTest, SingleChunk)
{
std::vector<uint8_t> data(10, 0xCC);

esp_err_t ret = cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
esp_err_t ret = cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);

EXPECT_EQ(ret, ESP_OK);
EXPECT_EQ(g_captured_packets.size(), 1u);
Expand All @@ -132,7 +132,7 @@ TEST_F(ChunkerTest, ThreeChunks)
const size_t data_len = max_payload * 2 + 1; // 確実に3チャンク
std::vector<uint8_t> data(data_len, 0xDD);

esp_err_t ret = cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
esp_err_t ret = cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);

EXPECT_EQ(ret, ESP_OK);
EXPECT_EQ(g_captured_packets.size(), 3u);
Expand All @@ -148,7 +148,7 @@ TEST_F(ChunkerTest, HeaderConsistency)
const size_t data_len = max_payload * 2 + 1; // 3チャンク
std::vector<uint8_t> data(data_len, 0x00);

cast_send_frame(data.data(), data.size(), CAST_FMT_RGB565, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_RGB565, 320, 240, &g_mock_transport);

ASSERT_EQ(g_captured_packets.size(), 3u);

Expand Down Expand Up @@ -189,6 +189,14 @@ TEST_F(ChunkerTest, HeaderConsistency)
EXPECT_EQ(h0->max_payload, max_payload);
EXPECT_EQ(h1->max_payload, max_payload);
EXPECT_EQ(h2->max_payload, max_payload);

// width/height は全パケットで同じ値
EXPECT_EQ(h0->width, 320);
EXPECT_EQ(h1->width, 320);
EXPECT_EQ(h2->width, 320);
EXPECT_EQ(h0->height, 240);
EXPECT_EQ(h1->height, 240);
EXPECT_EQ(h2->height, 240);
}

// ===========================================================================
Expand All @@ -206,7 +214,7 @@ TEST_F(ChunkerTest, PayloadBoundary)
data[i] = (uint8_t)(i & 0xFF);
}

cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);

ASSERT_EQ(g_captured_packets.size(), 3u);

Expand Down Expand Up @@ -251,13 +259,13 @@ TEST_F(ChunkerTest, FrameIdIncrements)
std::vector<uint8_t> data(10, 0xFF);

// 1回目
cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);
ASSERT_EQ(g_captured_packets.size(), 1u);
uint16_t frame_id_1 = packet_header(0)->frame_id;

// 2回目
g_captured_packets.clear();
cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);
ASSERT_EQ(g_captured_packets.size(), 1u);
uint16_t frame_id_2 = packet_header(0)->frame_id;

Expand All @@ -275,7 +283,7 @@ TEST_F(ChunkerTest, SendFailureMidway)
std::vector<uint8_t> data(max_payload * 2 + 1, 0xEE); // 3チャンク
g_send_fail_at = 1; // 2番目(index=1)のsendで失敗

esp_err_t ret = cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
esp_err_t ret = cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);

EXPECT_EQ(ret, ESP_FAIL);
// 1番目のパケットだけキャプチャされている(2番目で失敗したのでpushされない)
Expand All @@ -288,7 +296,7 @@ TEST_F(ChunkerTest, SendFailureOnFirst)
std::vector<uint8_t> data(10, 0x00);
g_send_fail_at = 0;

esp_err_t ret = cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
esp_err_t ret = cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);

EXPECT_EQ(ret, ESP_FAIL);
EXPECT_EQ(g_captured_packets.size(), 0u);
Expand All @@ -302,7 +310,7 @@ TEST_F(ChunkerTest, PacketSizeNeverExceedsMtu)
{
std::vector<uint8_t> data(500, 0x42);

cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);

for (size_t i = 0; i < g_captured_packets.size(); i++) {
EXPECT_LE(g_captured_packets[i].size(), g_mock_mtu)
Expand All @@ -320,7 +328,7 @@ TEST_F(ChunkerTest, SmallMtu)
const size_t max_payload = calc_max_payload();
std::vector<uint8_t> data(50, 0x11);

cast_send_frame(data.data(), data.size(), CAST_FMT_GRAYSCALE, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_GRAYSCALE, 320, 240, &g_mock_transport);

size_t expected_chunks = calc_expected_chunks(50);
EXPECT_EQ(g_captured_packets.size(), expected_chunks);
Expand All @@ -337,7 +345,7 @@ TEST_F(ChunkerTest, LargeMtu)
const size_t max_payload = calc_max_payload();
std::vector<uint8_t> data(200, 0x22);

cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);

// 200 < max_payload → 1チャンク
EXPECT_EQ(g_captured_packets.size(), 1u);
Expand Down
46 changes: 28 additions & 18 deletions test/host/test_cast_reassembler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,19 @@ static cast_transport_interface_t g_mock_transport = {
struct CapturedFrame {
std::vector<uint8_t> data;
cast_image_format_t fmt;
uint16_t width;
uint16_t height;
};

static std::vector<CapturedFrame> g_completed_frames;

static void on_frame_ready(const uint8_t *data, size_t len, cast_image_format_t fmt)
static void on_frame_ready(const uint8_t *data, size_t len, cast_image_format_t fmt, uint16_t width, uint16_t height)
{
CapturedFrame f;
f.data.assign(data, data + len);
f.fmt = fmt;
f.width = width;
f.height = height;
g_completed_frames.push_back(std::move(f));
}

Expand Down Expand Up @@ -121,7 +125,7 @@ class ReassemblerTest : public ::testing::Test {
TEST_F(ReassemblerTest, LoopbackMinimal)
{
uint8_t data[] = {0x42};
cast_send_frame(data, 1, CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(data, 1, CAST_FMT_JPEG, 0, 0, &g_mock_transport);
ASSERT_EQ(g_captured_packets.size(), 1u);

feed_all_packets();
Expand All @@ -138,7 +142,7 @@ TEST_F(ReassemblerTest, LoopbackExactMtu)
std::vector<uint8_t> data(max_payload);
std::iota(data.begin(), data.end(), 0);

cast_send_frame(data.data(), data.size(), CAST_FMT_RGB565, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_RGB565, 320, 240, &g_mock_transport);
ASSERT_EQ(g_captured_packets.size(), 1u);

feed_all_packets();
Expand All @@ -154,7 +158,7 @@ TEST_F(ReassemblerTest, LoopbackMtuPlusOne)
std::vector<uint8_t> data(max_payload + 1);
std::iota(data.begin(), data.end(), 0);

cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);
ASSERT_EQ(g_captured_packets.size(), 2u);

feed_all_packets();
Expand All @@ -169,7 +173,7 @@ TEST_F(ReassemblerTest, LoopbackJpeg16x16)
auto jpeg = read_file(std::string(TEST_DATA_DIR) + "/placeholder_jp_16x16.jpg");
ASSERT_FALSE(jpeg.empty());

cast_send_frame(jpeg.data(), jpeg.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(jpeg.data(), jpeg.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);
feed_all_packets();

ASSERT_EQ(g_completed_frames.size(), 1u);
Expand All @@ -184,7 +188,7 @@ TEST_F(ReassemblerTest, LoopbackJpeg200x200)
auto jpeg = read_file(std::string(TEST_DATA_DIR) + "/placeholder_jp_200x200.jpg");
ASSERT_FALSE(jpeg.empty());

cast_send_frame(jpeg.data(), jpeg.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(jpeg.data(), jpeg.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);
feed_all_packets();

ASSERT_EQ(g_completed_frames.size(), 1u);
Expand All @@ -202,7 +206,7 @@ TEST_F(ReassemblerTest, LoopbackJpeg640x480_LargeMtu)
auto jpeg = read_file(std::string(TEST_DATA_DIR) + "/placeholder_jp_640x480.jpg");
ASSERT_FALSE(jpeg.empty());

cast_send_frame(jpeg.data(), jpeg.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(jpeg.data(), jpeg.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);
feed_all_packets();

ASSERT_EQ(g_completed_frames.size(), 1u);
Expand All @@ -221,7 +225,7 @@ TEST_F(ReassemblerTest, ReverseOrder)
std::vector<uint8_t> data(max_payload * 2 + 10);
std::iota(data.begin(), data.end(), 0);

cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);
ASSERT_EQ(g_captured_packets.size(), 3u);

// 逆順: [2] → [1] → [0]
Expand All @@ -237,7 +241,7 @@ TEST_F(ReassemblerTest, RandomOrder)
auto jpeg = read_file(std::string(TEST_DATA_DIR) + "/placeholder_jp_200x200.jpg");
ASSERT_FALSE(jpeg.empty());

cast_send_frame(jpeg.data(), jpeg.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(jpeg.data(), jpeg.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);
size_t n = g_captured_packets.size();
ASSERT_GT(n, 2u);

Expand Down Expand Up @@ -269,7 +273,7 @@ TEST_F(ReassemblerTest, FrameIdSwitchResetsState)
std::iota(data2.begin(), data2.end(), 100);

// フレーム1を送信(3チャンク)
cast_send_frame(data1.data(), data1.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(data1.data(), data1.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);
size_t frame1_count = g_captured_packets.size();

// フレーム1のチャンク0だけ投入(未完成)
Expand All @@ -278,7 +282,7 @@ TEST_F(ReassemblerTest, FrameIdSwitchResetsState)

// フレーム2を送信
g_captured_packets.clear();
cast_send_frame(data2.data(), data2.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(data2.data(), data2.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);

// フレーム2の全チャンクを投入 → フレーム1がリセットされフレーム2が完成
feed_all_packets();
Expand All @@ -302,7 +306,7 @@ TEST_F(ReassemblerTest, WrongMagicIgnored)
const size_t max_payload = g_mock_mtu - CAST_PROTOCOL_OVERHEAD;
std::vector<uint8_t> data(10, 0xAA);

cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);
ASSERT_EQ(g_captured_packets.size(), 1u);

// magicバイトを壊す
Expand All @@ -320,7 +324,7 @@ TEST_F(ReassemblerTest, OutOfBoundsChunkIndexIgnored)
std::vector<uint8_t> data(max_payload * 2 + 1);
std::iota(data.begin(), data.end(), 0);

cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);
ASSERT_EQ(g_captured_packets.size(), 3u);

// チャンク0を投入(フレーム初期化)
Expand Down Expand Up @@ -350,26 +354,32 @@ TEST_F(ReassemblerTest, FormatPropagation)
std::vector<uint8_t> data(50, 0x11);

// JPEG
cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);
feed_all_packets();
ASSERT_EQ(g_completed_frames.size(), 1u);
EXPECT_EQ(g_completed_frames[0].fmt, CAST_FMT_JPEG);
EXPECT_EQ(g_completed_frames[0].width, 0);
EXPECT_EQ(g_completed_frames[0].height, 0);

// RGB565
g_captured_packets.clear();
g_completed_frames.clear();
cast_send_frame(data.data(), data.size(), CAST_FMT_RGB565, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_RGB565, 320, 240, &g_mock_transport);
feed_all_packets();
ASSERT_EQ(g_completed_frames.size(), 1u);
EXPECT_EQ(g_completed_frames[0].fmt, CAST_FMT_RGB565);
EXPECT_EQ(g_completed_frames[0].width, 320);
EXPECT_EQ(g_completed_frames[0].height, 240);

// GRAYSCALE
g_captured_packets.clear();
g_completed_frames.clear();
cast_send_frame(data.data(), data.size(), CAST_FMT_GRAYSCALE, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_GRAYSCALE, 160, 120, &g_mock_transport);
feed_all_packets();
ASSERT_EQ(g_completed_frames.size(), 1u);
EXPECT_EQ(g_completed_frames[0].fmt, CAST_FMT_GRAYSCALE);
EXPECT_EQ(g_completed_frames[0].width, 160);
EXPECT_EQ(g_completed_frames[0].height, 120);
}

// 4-2: 最終サイズの完全一致(連番データ)
Expand All @@ -380,7 +390,7 @@ TEST_F(ReassemblerTest, FinalSizeExact)
std::vector<uint8_t> data(max_payload * 3 + 7);
std::iota(data.begin(), data.end(), 0);

cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);
feed_all_packets();

ASSERT_EQ(g_completed_frames.size(), 1u);
Expand All @@ -395,7 +405,7 @@ TEST_F(ReassemblerTest, FinalSizeExactReverseOrder)
std::vector<uint8_t> data(max_payload * 3 + 7);
std::iota(data.begin(), data.end(), 0);

cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, &g_mock_transport);
cast_send_frame(data.data(), data.size(), CAST_FMT_JPEG, 0, 0, &g_mock_transport);
size_t n = g_captured_packets.size();

// 逆順投入
Expand Down