From d88498e4fdee7f326b7bc055eff97527a49083b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Mangano?= Date: Fri, 27 Jan 2023 15:17:00 +0900 Subject: [PATCH] Renumber the pages past the OpusTags packet We will soon be able to process OpusTags packets spanning multiple pages, which would offset the page number of all the succeeding pages. This change prepares the process loop for that feature. --- src/cli.cc | 9 +++++++++ src/ogg.cc | 19 +++++++++++++++++++ src/opus.cc | 14 -------------- src/opustags.h | 22 ++++++++++++++++++++++ t/ogg.cc | 19 ++++++++++++++++++- 5 files changed, 68 insertions(+), 15 deletions(-) diff --git a/src/cli.cc b/src/cli.cc index 26418cf..81b28e3 100644 --- a/src/cli.cc +++ b/src/cli.cc @@ -358,6 +358,13 @@ static void process(ot::ogg_reader& reader, ot::ogg_writer* writer, const ot::op { bool focused = false; /*< the stream on which we operate is defined */ int focused_serialno; /*< when focused, the serialno of the focused stream */ + + /** When the number of pages the OpusTags packet takes differs from the input stream to the + * output stream, we need to renumber all the succeeding pages. If the input stream + * contains gaps, the offset will naively reproduce the gaps: page numbers 0 (1) 2 4 will + * become 0 (1 2) 3 5, where (…) is the OpusTags packet, and not 0 (1 2) 3 4. */ + int pageno_offset = 0; + while (reader.next_page()) { auto serialno = ogg_page_serialno(&reader.page); auto pageno = ogg_page_pageno(&reader.page); @@ -384,11 +391,13 @@ static void process(ot::ogg_reader& reader, ot::ogg_writer* writer, const ot::op } auto packet = ot::render_tags(tags); writer->write_header_packet(serialno, pageno, packet); + pageno_offset = writer->next_page_no - 1 - reader.absolute_page_no; } else { ot::print_comments(tags.comments, stdout, opt.raw); break; } } else if (writer) { + ot::renumber_page(reader.page, pageno + pageno_offset); writer->write_page(reader.page); } } diff --git a/src/ogg.cc b/src/ogg.cc index 068b99c..cc149ee 100644 --- a/src/ogg.cc +++ b/src/ogg.cc @@ -86,6 +86,12 @@ void ot::ogg_writer::write_page(const ogg_page& page) { if (page.header_len < 0 || page.body_len < 0) throw status {st::int_overflow, "Overflowing page length"}; + + long pageno = ogg_page_pageno(&page); + if (pageno != next_page_no) + fprintf(stderr, "Output page number mismatch: expected %ld, got %ld.\n", next_page_no, pageno); + next_page_no = pageno + 1; + auto header_len = static_cast(page.header_len); auto body_len = static_cast(page.body_len); if (fwrite(page.header, 1, header_len, file) < header_len) @@ -116,3 +122,16 @@ void ot::ogg_writer::write_header_packet(int serialno, int pageno, ogg_packet& p if (ogg_stream_check(&stream) != 0) throw status {st::libogg_error, "ogg_stream_check failed"}; } + +void ot::renumber_page(ogg_page& page, long new_pageno) +{ + // Quick optimization: don’t bother recomputing the CRC if the pageno did not change. + long old_pageno = ogg_page_pageno(&page); + if (old_pageno == new_pageno) + return; + + /** The pageno field is located at bytes 18 to 21 (0-indexed, little-endian). */ + uint32_t le_pageno = htole32(new_pageno); + memcpy(&page.header[18], &le_pageno, 4); + ogg_page_checksum_set(&page); +} diff --git a/src/opus.cc b/src/opus.cc index 1b38a2e..40da782 100644 --- a/src/opus.cc +++ b/src/opus.cc @@ -25,20 +25,6 @@ #include -#ifdef HAVE_ENDIAN_H -# include -#endif - -#ifdef HAVE_SYS_ENDIAN_H -# include -#endif - -#ifdef __APPLE__ -#include -#define htole32(x) OSSwapHostToLittleInt32(x) -#define le32toh(x) OSSwapLittleToHostInt32(x) -#endif - ot::opus_tags ot::parse_tags(const ogg_packet& packet) { if (packet.bytes < 0) diff --git a/src/opustags.h b/src/opustags.h index 7b6c793..d9b9cc1 100644 --- a/src/opustags.h +++ b/src/opustags.h @@ -39,6 +39,20 @@ #include #include +#ifdef HAVE_ENDIAN_H +# include +#endif + +#ifdef HAVE_SYS_ENDIAN_H +# include +#endif + +#ifdef __APPLE__ +#include +#define htole32(x) OSSwapHostToLittleInt32(x) +#define le32toh(x) OSSwapLittleToHostInt32(x) +#endif + namespace ot { /** @@ -299,6 +313,11 @@ struct ogg_writer { * Path to the output file. */ std::optional path; + /** + * Custom counter for the sequential page number to be written. It allows us to detect + * ogg_page_pageno mismatches and renumber the pages if needed. + */ + long next_page_no = 0; }; /** @@ -318,6 +337,9 @@ private: std::unique_ptr data; }; +/** Update the Ogg pageno field in the given page. The CRC is recomputed if needed. */ +void renumber_page(ogg_page& page, long new_pageno); + /** \} */ /***********************************************************************************************//** diff --git a/t/ogg.cc b/t/ogg.cc index d7504de..510d063 100644 --- a/t/ogg.cc +++ b/t/ogg.cc @@ -139,12 +139,29 @@ void check_identification() throw failure("was not the beginning of a stream"); } +void check_renumber_page() +{ + ot::file input = fopen("gobble.opus", "r"); + if (input == nullptr) + throw failure("could not open gobble.opus"); + + ot::ogg_reader reader(input.get()); + if (reader.next_page() != true) + throw failure("could not read the first page"); + + long new_pageno = 1234; + ot::renumber_page(reader.page, new_pageno); + if (ogg_page_pageno(&reader.page) != new_pageno) + throw failure("renumbering failed"); +} + int main(int argc, char **argv) { - std::cout << "1..4\n"; + std::cout << "1..5\n"; run(check_ref_ogg, "check a reference ogg stream"); run(check_memory_ogg, "build and check a fresh stream"); run(check_bad_stream, "read a non-ogg stream"); run(check_identification, "stream identification"); + run(check_renumber_page, "page renumbering"); return 0; }