diff --git a/include/cast_protocol.h b/include/cast_protocol.h index 57c7567..c4354db 100644 --- a/include/cast_protocol.h +++ b/include/cast_protocol.h @@ -30,6 +30,8 @@ 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など) */ @@ -37,14 +39,21 @@ 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 受信側の初期化 diff --git a/src/cast_chunker.c b/src/cast_chunker.c index eb80bd9..535255a 100644 --- a/src/cast_chunker.c +++ b/src/cast_chunker.c @@ -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; @@ -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. データのコピー(ヘッダの直後へ) diff --git a/src/cast_internal.h b/src/cast_internal.h index 2963250..1294004 100644 --- a/src/cast_internal.h +++ b/src/cast_internal.h @@ -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; diff --git a/src/cast_reassembler.c b/src/cast_reassembler.c index 5fff28e..f703ed6 100644 --- a/src/cast_reassembler.c +++ b/src/cast_reassembler.c @@ -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; @@ -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; } @@ -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 から戻ったらミドルウェア側で解放 diff --git a/test/host/test_cast_chunker.cpp b/test/host/test_cast_chunker.cpp index 5e79f8f..365a5e5 100644 --- a/test/host/test_cast_chunker.cpp +++ b/test/host/test_cast_chunker.cpp @@ -95,7 +95,7 @@ TEST_F(ChunkerTest, ExactFitChunkCount) const size_t data_len = max_payload * 2; std::vector 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); @@ -108,7 +108,7 @@ TEST_F(ChunkerTest, PartialLastChunkCount) // max_payload + 1 バイトで確実に2チャンク std::vector 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); @@ -119,7 +119,7 @@ TEST_F(ChunkerTest, SingleChunk) { std::vector 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); @@ -132,7 +132,7 @@ TEST_F(ChunkerTest, ThreeChunks) const size_t data_len = max_payload * 2 + 1; // 確実に3チャンク std::vector 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); @@ -148,7 +148,7 @@ TEST_F(ChunkerTest, HeaderConsistency) const size_t data_len = max_payload * 2 + 1; // 3チャンク std::vector 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); @@ -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); } // =========================================================================== @@ -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); @@ -251,13 +259,13 @@ TEST_F(ChunkerTest, FrameIdIncrements) std::vector 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; @@ -275,7 +283,7 @@ TEST_F(ChunkerTest, SendFailureMidway) std::vector 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されない) @@ -288,7 +296,7 @@ TEST_F(ChunkerTest, SendFailureOnFirst) std::vector 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); @@ -302,7 +310,7 @@ TEST_F(ChunkerTest, PacketSizeNeverExceedsMtu) { std::vector 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) @@ -320,7 +328,7 @@ TEST_F(ChunkerTest, SmallMtu) const size_t max_payload = calc_max_payload(); std::vector 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); @@ -337,7 +345,7 @@ TEST_F(ChunkerTest, LargeMtu) const size_t max_payload = calc_max_payload(); std::vector 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); diff --git a/test/host/test_cast_reassembler.cpp b/test/host/test_cast_reassembler.cpp index 9c29d19..b0635e6 100644 --- a/test/host/test_cast_reassembler.cpp +++ b/test/host/test_cast_reassembler.cpp @@ -52,15 +52,19 @@ static cast_transport_interface_t g_mock_transport = { struct CapturedFrame { std::vector data; cast_image_format_t fmt; + uint16_t width; + uint16_t height; }; static std::vector 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)); } @@ -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(); @@ -138,7 +142,7 @@ TEST_F(ReassemblerTest, LoopbackExactMtu) std::vector 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(); @@ -154,7 +158,7 @@ TEST_F(ReassemblerTest, LoopbackMtuPlusOne) std::vector 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(); @@ -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); @@ -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); @@ -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); @@ -221,7 +225,7 @@ TEST_F(ReassemblerTest, ReverseOrder) std::vector 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] @@ -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); @@ -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だけ投入(未完成) @@ -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(); @@ -302,7 +306,7 @@ TEST_F(ReassemblerTest, WrongMagicIgnored) const size_t max_payload = g_mock_mtu - CAST_PROTOCOL_OVERHEAD; std::vector 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バイトを壊す @@ -320,7 +324,7 @@ TEST_F(ReassemblerTest, OutOfBoundsChunkIndexIgnored) std::vector 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を投入(フレーム初期化) @@ -350,26 +354,32 @@ TEST_F(ReassemblerTest, FormatPropagation) std::vector 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: 最終サイズの完全一致(連番データ) @@ -380,7 +390,7 @@ TEST_F(ReassemblerTest, FinalSizeExact) std::vector 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); @@ -395,7 +405,7 @@ TEST_F(ReassemblerTest, FinalSizeExactReverseOrder) std::vector 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(); // 逆順投入