Добро пожаловать, гость ( Вход | Регистрация )
![]() ![]() |
| Lumis |
Mar 2 2026, 22:52
Сообщение
#1
|
|
Member ![]() ![]() Группа: Authorized Сообщений: 11 Регистрация: 2-March 26 Пользователь №: 18,038 Спасибо сказали: 11 раз(а) |
Всем привет. Коллеги написали прекрасный распаковщик файлов ресурсов, за что им громадное спасибо.
А у меня получилось разобрать странный формат их видеофайлов ".avi", которые никак на традиционные AVI от майков не похожи. По предложению ув. -=CHE@TER=- выкладываю вариант перегонки "DV!!" или "dvbang" в контейнер OGM без переупаковки потока, без добавления таймкодов и пр. Конвертор грязноват: вопроизводится нормально, но ffmpeg генерирует много ошибок "[aist#0:0/vorbis @ 000002192aad3680] [dec:vorbis @ 000002192aa71740] Error submitting packet to decoder: Invalid data found when processing input". Есть также вариант переупаковки с помощью ffmpeg без переконвертации для добавления таймкодов. Код в значительной степени сгенерирован AI. Лицензия - MIT. Сборка макс. компактного бинарника в VC Express: CODE cl /TC /O1 /Os /GS- /Gy /GL dvbang2ogv.c /link /LTCG /OPT:REF /OPT:ICF /SUBSYSTEM:CONSOLE CODE Copyright 2026 Lumis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CODE /* dvbang2ogv.c * ANSI C / C89, Visual Studio 2008 friendly * License: MIT, Copyright 2026 Lumis * * Converts "DV!!" .dv files (non-standard container) into Ogg/OGM (.ogv) without * using ffmpeg for muxing. Video is MPEG-4 Part 2 elementary stream kept byte-exact. * Audio is Ogg/Vorbis pages copied byte-exact (no re-encode). * * Matches the latest Python + C++ variants: * - DV!! header: 19 LE u32 (76 bytes), hdr[0]='DV!!', hdr[4]=w, hdr[5]=h, * hdr[7]=packet_count N, hdr[9]=audio_preroll_bytes M. * - Table: N entries * 20 bytes (5 LE u32 each): * size = entry0 & 0x7fffffff * audio_bytes = entry[3] * - Data: preroll (M bytes) + N packets: * video += packet[0 : size-audio_bytes] * audio += packet[size-audio_bytes : size] * * OGV/OGM mux: * - Copy audio BOS page unchanged * - Write video BOS (0x01 + ogm stream_header, subtype "XVID") * - Write video comment (0x03 + vorbis-style vendor comment, no user comments) * - Copy remaining Vorbis header pages unchanged (until 3 Vorbis packets end) * - Interleave: * at = audio_page.granule / sample_rate * emit video frames while ((v_i+1)/fps) <= at + (0.5/fps) * then copy audio page * Finally flush remaining video frames. * * Determinism: * - video serial hardcoded to 0xC0DEC0DE; if equals audio serial => xor with 0xFFFFFFFF. * * Optional: * --validate : runs "ffmpeg ... -f null -" via system() (disabled by default) */ #include <stdio.h> #include <stdlib.h> #include <string.h> /* VS2008: snprintf is _snprintf */ #ifndef snprintf #define snprintf _snprintf #endif /* ---------- Fixed constants ---------- */ #define MAGIC_DV_LE 0x21215644UL /* "DV!!" little-endian */ #define VIDEO_SERIAL_FIXED 0xC0DEC0DEUL /* ---------- Basic types ---------- */ typedef unsigned char u8; typedef unsigned short u16; typedef unsigned long u32; /* Win32: 32-bit */ typedef unsigned __int64 u64; typedef struct { u8* p; u32 len; u32 cap; } Buf; typedef struct { u8 header_type; u64 granule; u32 serial; u32 seq; u8* segtable; u32 segtable_len; u8* body; u32 body_len; u8* bytes; u32 bytes_len; } OggPage; typedef struct { u8* p; u32 len; } Span; /* ---------- Errors ---------- */ static void die(const char* msg) { fprintf(stderr, "ERROR: %s\n", msg); exit(2); } static void die2(const char* msg, const char* more) { fprintf(stderr, "ERROR: %s%s\n", msg, more ? more : ""); exit(2); } /* ---------- LE helpers ---------- */ static u32 rd32le(const u8* p) { return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24); } static u64 rd64le(const u8* p) { u64 v; v = (u64)p[0]; v |= (u64)p[1] << 8; v |= (u64)p[2] << 16; v |= (u64)p[3] << 24; v |= (u64)p[4] << 32; v |= (u64)p[5] << 40; v |= (u64)p[6] << 48; v |= (u64)p[7] << 56; return v; } static void wr32le(u8* p, u32 v) { p[0] = (u8)(v & 0xFF); p[1] = (u8)((v >> 8) & 0xFF); p[2] = (u8)((v >> 16) & 0xFF); p[3] = (u8)((v >> 24) & 0xFF); } static void wr64le(u8* p, u64 v) { p[0] = (u8)(v & 0xFF); p[1] = (u8)((v >> 8) & 0xFF); p[2] = (u8)((v >> 16) & 0xFF); p[3] = (u8)((v >> 24) & 0xFF); p[4] = (u8)((v >> 32) & 0xFF); p[5] = (u8)((v >> 40) & 0xFF); p[6] = (u8)((v >> 48) & 0xFF); p[7] = (u8)((v >> 56) & 0xFF); } /* ---------- Buf ---------- */ static void buf_init(Buf* b ) { b->p = 0; b->len = 0; b->cap = 0; } static void buf_reserve(Buf* b, u32 need) { u32 ncap; if (need <= b->cap) return; ncap = b->cap ? (b->cap + (b->cap >> 1) + 64) : 256; if (ncap < need) ncap = need; b->p = (u8*)realloc(b->p, ncap); if (!b->p) die("Out of memory"); b->cap = ncap; } static void buf_append(Buf* b, const u8* data, u32 n) { if (n == 0) return; buf_reserve(b, b->len + n); memcpy(b->p + b->len, data, n); b->len += n; } static void buf_append_u8(Buf* b, u8 v) { buf_reserve(b, b->len + 1); b->p[b->len++] = v; } static void buf_free(Buf* b ) { if (b->p) free(b->p); b->p = 0; b->len = 0; b->cap = 0; } /* ---------- File I/O ---------- */ static u8* read_file_all(const char* path, u32* out_len) { FILE* f; long sz; u8* data; f = fopen(path, "rb"); if (!f) return 0; fseek(f, 0, SEEK_END); sz = ftell(f); if (sz < 0) { fclose(f); return 0; } fseek(f, 0, SEEK_SET); data = (u8*)malloc((u32)sz); if (!data) { fclose(f); return 0; } if (sz > 0 && fread(data, 1, (u32)sz, f) != (u32)sz) { free(data); fclose(f); return 0; } fclose(f); *out_len = (u32)sz; return data; } /* ---------- Ogg CRC ---------- */ static u32 CRC_TABLE[256]; static void crc_table_init(void) { u32 poly, i, j, r; poly = 0x04C11DB7UL; for (i = 0; i < 256; i++) { r = i << 24; for (j = 0; j < 8; j++) { if (r & 0x80000000UL) r = (r << 1) ^ poly; else r <<= 1; } CRC_TABLE[i] = r; } } static u32 ogg_crc(const u8* page, u32 len) { u32 crc, i; crc = 0; for (i = 0; i < len; i++) { u8 b = page[i]; crc = (crc << 8) ^ CRC_TABLE[((crc >> 24) & 0xFF) ^ b]; } return crc; } /* ---------- Ogg page builder ---------- * Builds one page containing pkt_count packets (commonly 1 packet per page here). */ static void ogg_build_page(Buf* out, const u8* const* pkts, const u32* pkt_lens, u32 pkt_count, u32 serial, u32 seq, u64 granule, u8 header_type) { u8 segs[255]; u32 seg_count, i; Buf body; Buf page; u8 hdr[27]; u32 crc; seg_count = 0; buf_init(&body); buf_init(&page); /* Build body and lacing values */ for (i = 0; i < pkt_count; i++) { u32 n; buf_append(&body, pkts[i], pkt_lens[i]); n = pkt_lens[i]; while (n >= 255) { if (seg_count >= 255) die("Too many segments in one Ogg page"); segs[seg_count++] = 255; n -= 255; } if (seg_count >= 255) die("Too many segments in one Ogg page"); segs[seg_count++] = (u8)n; } /* Header */ memset(hdr, 0, sizeof(hdr)); hdr[0] = 'O'; hdr[1] = 'g'; hdr[2] = 'g'; hdr[3] = 'S'; hdr[4] = 0; /* version */ hdr[5] = header_type; wr64le(hdr + 6, granule); wr32le(hdr + 14, serial); wr32le(hdr + 18, seq); wr32le(hdr + 22, 0); /* CRC placeholder */ hdr[26] = (u8)seg_count; buf_append(&page, hdr, 27); buf_append(&page, segs, seg_count); buf_append(&page, body.p, body.len); /* Patch CRC */ crc = ogg_crc(page.p, page.len); wr32le(page.p + 22, crc); buf_append(out, page.p, page.len); buf_free(&page); buf_free(&body); } /* ---------- Ogg page parser (minimal) ---------- */ static OggPage* ogg_parse_pages(const u8* data, u32 len, u32* out_count) { OggPage* pages; u32 cap, count, i; cap = 64; count = 0; pages = (OggPage*)calloc(cap, sizeof(OggPage)); if (!pages) die("Out of memory"); i = 0; while (1) { u32 j, k, body_len, body_off, seg_off; int found; u8 nseg; u8 header_type; u64 granule; u32 serial, seq; found = 0; for (j = i; j + 4 <= len; j++) { if (data[j]=='O' && data[j+1]=='g' && data[j+2]=='g' && data[j+3]=='S') { found = 1; break; } } if (!found) break; if (j + 27 > len) break; header_type = data[j + 5]; granule = rd64le(data + j + 6); serial = rd32le(data + j + 14); seq = rd32le(data + j + 18); nseg = data[j + 26]; seg_off = j + 27; body_off = seg_off + (u32)nseg; if (body_off > len) break; body_len = 0; for (k = 0; k < (u32)nseg; k++) body_len += data[seg_off + k]; if (body_off + body_len > len) break; if (count >= cap) { cap = cap + cap/2 + 32; pages = (OggPage*)realloc(pages, cap * sizeof(OggPage)); if (!pages) die("Out of memory"); } pages[count].header_type = header_type; pages[count].granule = granule; pages[count].serial = serial; pages[count].seq = seq; pages[count].segtable_len = (u32)nseg; pages[count].segtable = (u8*)malloc((u32)nseg); if (!pages[count].segtable) die("Out of memory"); memcpy(pages[count].segtable, data + seg_off, (u32)nseg); pages[count].body_len = body_len; pages[count].body = (u8*)malloc(body_len); if (!pages[count].body) die("Out of memory"); memcpy(pages[count].body, data + body_off, body_len); pages[count].bytes_len = (body_off + body_len) - j; pages[count].bytes = (u8*)malloc(pages[count].bytes_len); if (!pages[count].bytes) die("Out of memory"); memcpy(pages[count].bytes, data + j, pages[count].bytes_len); count++; i = body_off + body_len; } *out_count = count; return pages; } static void ogg_free_pages(OggPage* pages, u32 count) { u32 i; for (i = 0; i < count; i++) { if (pages[i].segtable) free(pages[i].segtable); if (pages[i].body) free(pages[i].body); if (pages[i].bytes) free(pages[i].bytes); } free(pages); } /* ---------- Vorbis helpers ---------- */ static u32 vorbis_detect_sample_rate(const OggPage* pages, u32 count) { Buf cur; u32 i, sidx; buf_init(&cur); for (i = 0; i < count; i++) { u32 off = 0; for (sidx = 0; sidx < pages[i].segtable_len; sidx++) { u8 s = pages[i].segtable[sidx]; buf_append(&cur, pages[i].body + off, (u32)s); off += (u32)s; if (s < 255) { if (cur.len >= 16 && cur.p[0] == 0x01 && cur.p[1]=='v' && cur.p[2]=='o' && cur.p[3]=='r' && cur.p[4]=='b' && cur.p[5]=='i' && cur.p[6]=='s') { u32 sr = rd32le(cur.p + 12); buf_free(&cur); return sr; } buf_free(&cur); return 0; } } } buf_free(&cur); return 0; } static u32 vorbis_header_end_page_index(const OggPage* pages, u32 count) { u32 pkt_count, i, sidx; pkt_count = 0; for (i = 0; i < count; i++) { for (sidx = 0; sidx < pages[i].segtable_len; sidx++) { if (pages[i].segtable[sidx] < 255) { pkt_count++; if (pkt_count >= 3) return i; } } } return (count ? (count - 1) : 0); } /* ---------- DV!! extraction ---------- */ static void dvbang_extract(const u8* dv, u32 dv_len, u32* out_w, u32* out_h, u8** out_video, u32* out_video_len, u8** out_audio, u32* out_audio_len, u32* out_packet_count) { u32 hdr[19]; u32 i; u32 N, M; u32 table_off, table_len, data_off; u32 pos; Buf video, audio; if (dv_len < 76) die("Input too small for DV!! header"); for (i = 0; i < 19; i++) hdr[i] = rd32le(dv + i*4); if (hdr[0] != MAGIC_DV_LE) die("Bad DV!! signature"); *out_w = hdr[4]; *out_h = hdr[5]; N = hdr[7]; M = hdr[9]; *out_packet_count = N; table_off = 76; table_len = N * 20UL; if (dv_len < table_off + table_len) die("Truncated: packet table"); data_off = table_off + table_len; if (dv_len < data_off + M) die("Truncated: preroll region"); buf_init(&video); buf_init(&audio); /* audio begins with preroll */ buf_append(&audio, dv + data_off, M); pos = data_off + M; for (i = 0; i < N; i++) { const u8* ent; u32 size_flags, size, audio_bytes; ent = dv + (table_off + i*20UL); size_flags = rd32le(ent); size = size_flags & 0x7FFFFFFFUL; audio_bytes = rd32le(ent + 12); if (pos + size > dv_len) die("Truncated: packet region"); if (audio_bytes > size) die("Corrupt: audio_bytes > size"); if (audio_bytes) { u32 vlen = size - audio_bytes; buf_append(&video, dv + pos, vlen); buf_append(&audio, dv + pos + vlen, audio_bytes); } else { buf_append(&video, dv + pos, size); } pos += size; } *out_video = video.p; *out_video_len = video.len; *out_audio = audio.p; *out_audio_len = audio.len; } /* ---------- MPEG-4 Part 2 frame splitting ---------- */ static Span* mpeg4_split_vop_frames(const u8* es, u32 es_len, u32* out_count) { const u8 vop0 = 0x00, vop1 = 0x00, vop2 = 0x01, vop3 = 0xB6; u32* idxs; u32 cap, count, i; Span* frames; u32 header_len; u8* header; cap = 1024; count = 0; idxs = (u32*)malloc(cap * sizeof(u32)); if (!idxs) die("Out of memory"); i = 0; while (i + 4 <= es_len) { u32 j; int found; found = 0; for (j = i; j + 4 <= es_len; j++) { if (es[j]==vop0 && es[j+1]==vop1 && es[j+2]==vop2 && es[j+3]==vop3) { found = 1; break; } } if (!found) break; if (count >= cap) { cap = cap + cap/2 + 256; idxs = (u32*)realloc(idxs, cap * sizeof(u32)); if (!idxs) die("Out of memory"); } idxs[count++] = j; i = j + 4; } if (count == 0) { free(idxs); die("No MPEG-4 VOP start codes (00 00 01 B6) found"); } header_len = idxs[0]; header = 0; if (header_len) { header = (u8*)malloc(header_len); if (!header) die("Out of memory"); memcpy(header, es, header_len); } frames = (Span*)calloc(count, sizeof(Span)); if (!frames) die("Out of memory"); for (i = 0; i < count; i++) { u32 start, end, flen; start = idxs[i]; end = (i + 1 < count) ? idxs[i+1] : es_len; flen = end - start; if (i == 0) { u32 total = header_len + flen; frames[i].p = (u8*)malloc(total); if (!frames[i].p) die("Out of memory"); if (header_len) memcpy(frames[i].p, header, header_len); memcpy(frames[i].p + header_len, es + start, flen); frames[i].len = total; } else { frames[i].p = (u8*)malloc(flen); if (!frames[i].p) die("Out of memory"); memcpy(frames[i].p, es + start, flen); frames[i].len = flen; } } if (header) free(header); free(idxs); *out_count = count; return frames; } static void free_frames(Span* frames, u32 count) { u32 i; for (i = 0; i < count; i++) if (frames[i].p) free(frames[i].p); free(frames); } /* ---------- OGM packet builders ---------- */ static void ogm_build_stream_header_video(u8 out52[52], int w, int h, int fps) { /* See ogmtools ogmstreams.h layout; kept identical to prior variants. */ double tu_d; __int64 time_unit; __int64 samples_per_unit; u32 default_len; u32 buffersize; u16 bits_per_sample; u16 padding; memset(out52, 0, 52); memcpy(out52 + 0, "video", 5); memcpy(out52 + 8, "XVID", 4); wr32le(out52 + 12, 52); tu_d = 10000000.0 / (double)fps; time_unit = (__int64)(tu_d + 0.5); samples_per_unit = 1; default_len = 1; buffersize = 0; bits_per_sample = 0; padding = 0; wr64le(out52 + 16, (u64)time_unit); wr64le(out52 + 24, (u64)samples_per_unit); wr32le(out52 + 32, default_len); wr32le(out52 + 36, buffersize); out52[40] = (u8)(bits_per_sample & 0xFF); out52[41] = (u8)((bits_per_sample >> 8) & 0xFF); wr32le(out52 + 42, (u32)w); wr32le(out52 + 46, (u32)h); out52[50] = (u8)(padding & 0xFF); out52[51] = (u8)((padding >> 8) & 0xFF); } static void ogm_make_video_header_pkt(Buf* pkt, int w, int h, int fps) { u8 sh[52]; buf_init(pkt); ogm_build_stream_header_video(sh, w, h, fps); buf_append_u8(pkt, 0x01); buf_append(pkt, sh, 52); } static void ogm_make_video_comment_pkt(Buf* pkt) { const char* vendor = "dvbang"; u32 vlen; u8 tmp[4]; vlen = (u32)strlen(vendor); buf_init(pkt); buf_append_u8(pkt, 0x03); wr32le(tmp, vlen); buf_append(pkt, tmp, 4); buf_append(pkt, (const u8*)vendor, vlen); wr32le(tmp, 0); buf_append(pkt, tmp, 4); } static void ogm_make_video_data_pkt(Buf* pkt, const u8* frame, u32 frame_len) { u8 flags; buf_init(pkt); /* lenbytes=2 (bit1 set => 0x80), keyframe=1 (0x08) */ flags = (u8)(0x80 | 0x08); buf_append_u8(pkt, flags); buf_append_u8(pkt, 1); /* duration samples=1 (LE16) */ buf_append_u8(pkt, 0); buf_append(pkt, frame, frame_len); } /* ---------- Mux (writes .ogv) ---------- */ static void mux_ogv(const char* out_path, const OggPage* audio_pages, u32 audio_page_count, u32 sample_rate, int w, int h, int fps, const Span* frames, u32 frame_count) { FILE* fo; Buf out; u32 audio_serial, video_serial, hdr_end; u32 vseq, v_i; double fps_d, half_frame; Buf vh, vc, vp; const u8* pkts1[1]; u32 lens1[1]; u32 ai; if (audio_page_count == 0) die("No audio pages"); audio_serial = audio_pages[0].serial; video_serial = VIDEO_SERIAL_FIXED; if (video_serial == audio_serial) video_serial ^= 0xFFFFFFFFUL; hdr_end = vorbis_header_end_page_index(audio_pages, audio_page_count); buf_init(&out); /* audio BOS copied */ buf_append(&out, audio_pages[0].bytes, audio_pages[0].bytes_len); /* video BOS */ ogm_make_video_header_pkt(&vh, w, h, fps); pkts1[0] = vh.p; lens1[0] = vh.len; ogg_build_page(&out, pkts1, lens1, 1, video_serial, 0, 0, 0x02); buf_free(&vh); vseq = 1; /* video comment */ ogm_make_video_comment_pkt(&vc); pkts1[0] = vc.p; lens1[0] = vc.len; ogg_build_page(&out, pkts1, lens1, 1, video_serial, vseq++, 0, 0x00); buf_free(&vc); /* remaining audio header pages unchanged */ for (ai = 1; ai <= hdr_end && ai < audio_page_count; ai++) { buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len); } fps_d = (double)fps; half_frame = 0.5 / fps_d; v_i = 0; /* interleave */ for (ai = hdr_end + 1; ai < audio_page_count; ai++) { double at; at = 0.0; if (sample_rate) at = (double)audio_pages[ai].granule / (double)sample_rate; while (v_i < frame_count) { double vt_next; vt_next = (double)(v_i + 1) / fps_d; if (vt_next <= at + half_frame) { u64 gran; u8 header_type; ogm_make_video_data_pkt(&vp, frames[v_i].p, frames[v_i].len); pkts1[0] = vp.p; lens1[0] = vp.len; gran = (u64)(v_i + 1); header_type = (u8)((v_i + 1 == frame_count) ? 0x04 : 0x00); ogg_build_page(&out, pkts1, lens1, 1, video_serial, vseq++, gran, header_type); buf_free(&vp); v_i++; } else { break; } } /* copy audio page */ buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len); } /* flush remaining video */ while (v_i < frame_count) { u64 gran; u8 header_type; ogm_make_video_data_pkt(&vp, frames[v_i].p, frames[v_i].len); pkts1[0] = vp.p; lens1[0] = vp.len; gran = (u64)(v_i + 1); header_type = (u8)((v_i + 1 == frame_count) ? 0x04 : 0x00); ogg_build_page(&out, pkts1, lens1, 1, video_serial, vseq++, gran, header_type); buf_free(&vp); v_i++; } /* write output */ fo = fopen(out_path, "wb"); if (!fo) die("Cannot open output file"); if (out.len && fwrite(out.p, 1, out.len, fo) != out.len) { fclose(fo); die("Write failed"); } fclose(fo); buf_free(&out); } /* ---------- CLI helpers ---------- */ static int streq(const char* a, const char* b ) { return strcmp(a,b ) == 0; } static void usage(const char* exe) { fprintf(stderr, "Usage:\n %s <input.dv> [output.ogv] [--validate]\n\n" "Options:\n" " --validate run ffmpeg decode test (optional; ffmpeg must be in PATH)\n" " -h,--help show help\n", exe); } static void make_default_out(const char* in, char* out, u32 outsz) { const char* dot; u32 n; dot = strrchr(in, '.'); if (!dot) { snprintf(out, outsz, "%s.ogv", in); return; } n = (u32)(dot - in); if (n + 4 + 1 >= outsz) die("Output path too long"); memcpy(out, in, n); memcpy(out + n, ".ogv", 5); } int main(int argc, char** argv) { const char* in_path; const char* out_arg; char out_path[1024]; int validate; int fps; u8* dv; u32 dv_len; u32 w, h, packet_count; u8* video_es; u32 video_es_len; u8* audio_ogg; u32 audio_ogg_len; OggPage* audio_pages; u32 audio_page_count; u32 sample_rate; Span* frames; u32 frame_count; int i; int positional; /* defaults */ in_path = 0; out_arg = 0; validate = 0; fps = 25; /* fixed as in your tested files */ if (argc < 2) { usage(argv[0]); return 2; } positional = 0; for (i = 1; i < argc; i++) { const char* a = argv[i]; if (streq(a, "-h") || streq(a, "--help")) { usage(argv[0]); return 0; } if (streq(a, "--validate")) { validate = 1; continue; } if (a[0] == '-') { fprintf(stderr, "Unknown option: %s\n\n", a); usage(argv[0]); return 2; } if (positional == 0) { in_path = a; positional++; continue; } if (positional == 1) { out_arg = a; positional++; continue; } fprintf(stderr, "Too many arguments.\n\n"); usage(argv[0]); return 2; } if (!in_path) { usage(argv[0]); return 2; } if (out_arg) { strncpy(out_path, out_arg, sizeof(out_path)-1); out_path[sizeof(out_path)-1] = 0; } else { make_default_out(in_path, out_path, (u32)sizeof(out_path)); } crc_table_init(); dv = read_file_all(in_path, &dv_len); if (!dv) die2("Cannot read input file: ", in_path); /* Extract */ w = h = packet_count = 0; video_es = 0; video_es_len = 0; audio_ogg = 0; audio_ogg_len = 0; dvbang_extract(dv, dv_len, &w, &h, &video_es, &video_es_len, &audio_ogg, &audio_ogg_len, &packet_count); /* Parse audio pages */ audio_pages = 0; audio_page_count = 0; audio_pages = ogg_parse_pages(audio_ogg, audio_ogg_len, &audio_page_count); if (!audio_pages || audio_page_count == 0) die("Audio parse failed: no Ogg pages"); /* Sample rate */ sample_rate = vorbis_detect_sample_rate(audio_pages, audio_page_count); if (sample_rate == 0) { fprintf(stderr, "WARNING: could not detect Vorbis sample rate; assuming 44100.\n"); sample_rate = 44100; } /* Split frames */ frames = mpeg4_split_vop_frames(video_es, video_es_len, &frame_count); /* Mux */ mux_ogv(out_path, audio_pages, audio_page_count, sample_rate, (int)w, (int)h, fps, frames, frame_count); printf("ogv: %s\n", out_path); printf("video: MPEG-4 Part 2 (%lux%lu, frames=%lu, fps=%d)\n", w, h, frame_count, fps); printf("audio: Vorbis (sr=%lu, pages=%lu)\n", sample_rate, audio_page_count); /* Optional validation */ if (validate) { char cmd[1400]; int rc; snprintf(cmd, sizeof(cmd), "ffmpeg -hide_banner -loglevel error -i \"%s\" -f null -", out_path); rc = system(cmd); if (rc != 0) { fprintf(stderr, "WARNING: ffmpeg validation failed (ffmpeg missing or decode errors).\n"); } } /* cleanup */ if (dv) free(dv); if (video_es) free(video_es); if (audio_ogg) free(audio_ogg); if (audio_pages) ogg_free_pages(audio_pages, audio_page_count); if (frames) free_frames(frames, frame_count); return 0; } Сразу же прикладываю вдогонку конвертор в более практичные хорошо перематываемые файлы (MKV + видеопоток MPEG-4 с добавленными таймкодами). Требует python + ffmpeg. Комментарии приветствуются. Спасибо сказали:
|
| -=CHE@TER=- |
Mar 2 2026, 23:32
Сообщение
#2
|
|
Walter Sullivan ![]() ![]() ![]() Группа: Root Admin Сообщений: 1,428 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 327 раз(а) |
Добро пожаловать!
Да, теперь всё отлично компилируется под ANSI C, без чемодана зависимостей, всего 18 Кб. Ещё раз огромное спасибо за программу! Глянул быстро - поменял только типы на стандартные из <stdint.h>. Конвертированные файлы проигрываются, но FFMPEG пишет следующие ошибки: CODE [NULL @ 025e19e0] [IMGUTILS @ 0022f404] Picture size 0x0 is invalid [NULL @ 025e19e0] Ignoring invalid width/height values MPlayer (линуксовый видеоплеер) пишет такое после окончания проигрывания: CODE [mpeg4 @ 01148f80]ac-tex damaged at 19 18 [mpeg4 @ 01148f80]Error at MB: 613 [mpeg4 @ 01148f80]concealing 222 DC, 222 AC, 222 MV errors Я завтра посмотрю подробнее что там, сейчас уже спать валюсь. А! Ключ "-hide_banner" убрал у ffmpeg при проверке - он не во всех версиях есть (как я понимаю, только у новых). Всякие ссылки по формату .OGM, которые я читал в прошлом году, когда думал такую программу сделать, если, вдруг, нужно. Ссылки на веб-архив, потому что половины сайтов уже нет. Описание формата там тоже не полное, но хотя бы кое-что понятнее становится. По второй ссылке ещё можно скачать программу для парсинга файлов .OGM и выводе информации о них. Пока не забыл - там основной подводный камень при описании структур был в том, что не учитывались выравнивающие байты. Например, поле указано как uint16_t (2 байта), смотришь в файл, а там ещё два нулевых байта (выравнивание на границу в 32 бита). Так что тут лучше брать существующие файлы, смотреть как они устроены и пытаться что-то похожее воспроизвести. CODE https://web.archive.org/web/20041210052652fw_/http://www.tobias.everwicked.com/packfmt.htm https://web.archive.org/web/20041014033857/http://www.shounen.ru/docs/ogm/ogm.shtml CODE /* dvbang2ogv.c * ANSI C / C89, Visual Studio 2008 friendly * License: MIT, Copyright 2026 Lumis * * Converts "DV!!" .dv files (non-standard container) into Ogg/OGM (.ogv) without * using ffmpeg for muxing. Video is MPEG-4 Part 2 elementary stream kept byte-exact. * Audio is Ogg/Vorbis pages copied byte-exact (no re-encode). * * Matches the latest Python + C++ variants: * - DV!! header: 19 LE u32 (76 bytes), hdr[0]='DV!!', hdr[4]=w, hdr[5]=h, * hdr[7]=packet_count N, hdr[9]=audio_preroll_bytes M. * - Table: N entries * 20 bytes (5 LE u32 each): * size = entry0 & 0x7fffffff * audio_bytes = entry[3] * - Data: preroll (M bytes) + N packets: * video += packet[0 : size-audio_bytes] * audio += packet[size-audio_bytes : size] * * OGV/OGM mux: * - Copy audio BOS page unchanged * - Write video BOS (0x01 + ogm stream_header, subtype "XVID") * - Write video comment (0x03 + vorbis-style vendor comment, no user comments) * - Copy remaining Vorbis header pages unchanged (until 3 Vorbis packets end) * - Interleave: * at = audio_page.granule / sample_rate * emit video frames while ((v_i+1)/fps) <= at + (0.5/fps) * then copy audio page * Finally flush remaining video frames. * * Determinism: * - video serial hardcoded to 0xC0DEC0DE; if equals audio serial => xor with 0xFFFFFFFF. * * Optional: * --validate : runs "ffmpeg ... -f null -" via system() (disabled by default) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> /* VS2008: snprintf is _snprintf */ #ifndef snprintf #define snprintf _snprintf #endif /* ---------- Fixed constants ---------- */ #define MAGIC_DV_LE 0x21215644UL /* "DV!!" little-endian */ #define VIDEO_SERIAL_FIXED 0xC0DEC0DEUL typedef struct { uint8_t* p; uint32_t len; uint32_t cap; } Buf; typedef struct { uint8_t header_type; uint64_t granule; uint32_t serial; uint32_t seq; uint8_t* segtable; uint32_t segtable_len; uint8_t* body; uint32_t body_len; uint8_t* bytes; uint32_t bytes_len; } OggPage; typedef struct { uint8_t* p; uint32_t len; } Span; /* ---------- Errors ---------- */ static void die(const char* msg) { printf("ERROR: %s\n", msg); exit(2); } static void die2(const char* msg, const char* more) { printf("ERROR: %s%s\n", msg, more ? more : ""); exit(2); } /* ---------- LE helpers ---------- */ static uint32_t rd32le(const uint8_t* p) { return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); } static uint64_t rd64le(const uint8_t* p) { uint64_t v; v = (uint64_t)p[0]; v |= (uint64_t)p[1] << 8; v |= (uint64_t)p[2] << 16; v |= (uint64_t)p[3] << 24; v |= (uint64_t)p[4] << 32; v |= (uint64_t)p[5] << 40; v |= (uint64_t)p[6] << 48; v |= (uint64_t)p[7] << 56; return v; } static void wr32le(uint8_t* p, uint32_t v) { p[0] = (uint8_t)(v & 0xFF); p[1] = (uint8_t)((v >> 8) & 0xFF); p[2] = (uint8_t)((v >> 16) & 0xFF); p[3] = (uint8_t)((v >> 24) & 0xFF); } static void wr64le(uint8_t* p, uint64_t v) { p[0] = (uint8_t)(v & 0xFF); p[1] = (uint8_t)((v >> 8) & 0xFF); p[2] = (uint8_t)((v >> 16) & 0xFF); p[3] = (uint8_t)((v >> 24) & 0xFF); p[4] = (uint8_t)((v >> 32) & 0xFF); p[5] = (uint8_t)((v >> 40) & 0xFF); p[6] = (uint8_t)((v >> 48) & 0xFF); p[7] = (uint8_t)((v >> 56) & 0xFF); } /* ---------- Buf ---------- */ static void buf_init(Buf* b ) { b->p = 0; b->len = 0; b->cap = 0; } static void buf_reserve(Buf* b, uint32_t need) { uint32_t ncap; if (need <= b->cap) return; ncap = b->cap ? (b->cap + (b->cap >> 1) + 64) : 256; if (ncap < need) ncap = need; b->p = (uint8_t*)realloc(b->p, ncap); if (!b->p) die("Out of memory"); b->cap = ncap; } static void buf_append(Buf* b, const uint8_t* data, uint32_t n) { if (n == 0) return; buf_reserve(b, b->len + n); memcpy(b->p + b->len, data, n); b->len += n; } static void buf_append_u8(Buf* b, uint8_t v) { buf_reserve(b, b->len + 1); b->p[b->len++] = v; } static void buf_free(Buf* b ) { if (b->p) free(b->p); b->p = 0; b->len = 0; b->cap = 0; } /* ---------- File I/O ---------- */ static uint8_t* read_file_all(const char* path, uint32_t* out_len) { FILE* f; long sz; uint8_t* data; f = fopen(path, "rb"); if (!f) return 0; fseek(f, 0, SEEK_END); sz = ftell(f); if (sz < 0) { fclose(f); return 0; } fseek(f, 0, SEEK_SET); data = (uint8_t*)malloc((uint32_t)sz); if (!data) { fclose(f); return 0; } if (sz > 0 && fread(data, 1, (uint32_t)sz, f) != (uint32_t)sz) { free(data); fclose(f); return 0; } fclose(f); *out_len = (uint32_t)sz; return data; } /* ---------- Ogg CRC ---------- */ static uint32_t CRC_TABLE[256]; static void crc_table_init(void) { uint32_t poly, i, j, r; poly = 0x04C11DB7UL; for (i = 0; i < 256; i++) { r = i << 24; for (j = 0; j < 8; j++) { if (r & 0x80000000UL) r = (r << 1) ^ poly; else r <<= 1; } CRC_TABLE[i] = r; } } static uint32_t ogg_crc(const uint8_t* page, uint32_t len) { uint32_t crc, i; crc = 0; for (i = 0; i < len; i++) { uint8_t b = page[i]; crc = (crc << 8) ^ CRC_TABLE[((crc >> 24) & 0xFF) ^ b]; } return crc; } /* ---------- Ogg page builder ---------- * Builds one page containing pkt_count packets (commonly 1 packet per page here). */ static void ogg_build_page(Buf* out, const uint8_t* const* pkts, const uint32_t* pkt_lens, uint32_t pkt_count, uint32_t serial, uint32_t seq, uint64_t granule, uint8_t header_type) { uint8_t segs[255]; uint32_t seg_count, i; Buf body; Buf page; uint8_t hdr[27]; uint32_t crc; seg_count = 0; buf_init(&body); buf_init(&page); /* Build body and lacing values */ for (i = 0; i < pkt_count; i++) { uint32_t n; buf_append(&body, pkts[i], pkt_lens[i]); n = pkt_lens[i]; while (n >= 255) { if (seg_count >= 255) die("Too many segments in one Ogg page"); segs[seg_count++] = 255; n -= 255; } if (seg_count >= 255) die("Too many segments in one Ogg page"); segs[seg_count++] = (uint8_t)n; } /* Header */ memset(hdr, 0, sizeof(hdr)); hdr[0] = 'O'; hdr[1] = 'g'; hdr[2] = 'g'; hdr[3] = 'S'; hdr[4] = 0; /* version */ hdr[5] = header_type; wr64le(hdr + 6, granule); wr32le(hdr + 14, serial); wr32le(hdr + 18, seq); wr32le(hdr + 22, 0); /* CRC placeholder */ hdr[26] = (uint8_t)seg_count; buf_append(&page, hdr, 27); buf_append(&page, segs, seg_count); buf_append(&page, body.p, body.len); /* Patch CRC */ crc = ogg_crc(page.p, page.len); wr32le(page.p + 22, crc); buf_append(out, page.p, page.len); buf_free(&page); buf_free(&body); } /* ---------- Ogg page parser (minimal) ---------- */ static OggPage* ogg_parse_pages(const uint8_t* data, uint32_t len, uint32_t* out_count) { OggPage* pages; uint32_t cap, count, i; cap = 64; count = 0; pages = (OggPage*)calloc(cap, sizeof(OggPage)); if (!pages) die("Out of memory"); i = 0; while (1) { uint32_t j, k, body_len, body_off, seg_off; int found; uint8_t nseg; uint8_t header_type; uint64_t granule; uint32_t serial, seq; found = 0; for (j = i; j + 4 <= len; j++) { if (data[j]=='O' && data[j+1]=='g' && data[j+2]=='g' && data[j+3]=='S') { found = 1; break; } } if (!found) break; if (j + 27 > len) break; header_type = data[j + 5]; granule = rd64le(data + j + 6); serial = rd32le(data + j + 14); seq = rd32le(data + j + 18); nseg = data[j + 26]; seg_off = j + 27; body_off = seg_off + (uint32_t)nseg; if (body_off > len) break; body_len = 0; for (k = 0; k < (uint32_t)nseg; k++) body_len += data[seg_off + k]; if (body_off + body_len > len) break; if (count >= cap) { cap = cap + cap/2 + 32; pages = (OggPage*)realloc(pages, cap * sizeof(OggPage)); if (!pages) die("Out of memory"); } pages[count].header_type = header_type; pages[count].granule = granule; pages[count].serial = serial; pages[count].seq = seq; pages[count].segtable_len = (uint32_t)nseg; pages[count].segtable = (uint8_t*)malloc((uint32_t)nseg); if (!pages[count].segtable) die("Out of memory"); memcpy(pages[count].segtable, data + seg_off, (uint32_t)nseg); pages[count].body_len = body_len; pages[count].body = (uint8_t*)malloc(body_len); if (!pages[count].body) die("Out of memory"); memcpy(pages[count].body, data + body_off, body_len); pages[count].bytes_len = (body_off + body_len) - j; pages[count].bytes = (uint8_t*)malloc(pages[count].bytes_len); if (!pages[count].bytes) die("Out of memory"); memcpy(pages[count].bytes, data + j, pages[count].bytes_len); count++; i = body_off + body_len; } *out_count = count; return pages; } static void ogg_free_pages(OggPage* pages, uint32_t count) { uint32_t i; for (i = 0; i < count; i++) { if (pages[i].segtable) free(pages[i].segtable); if (pages[i].body) free(pages[i].body); if (pages[i].bytes) free(pages[i].bytes); } free(pages); } /* ---------- Vorbis helpers ---------- */ static uint32_t vorbis_detect_sample_rate(const OggPage* pages, uint32_t count) { Buf cur; uint32_t i, sidx; buf_init(&cur); for (i = 0; i < count; i++) { uint32_t off = 0; for (sidx = 0; sidx < pages[i].segtable_len; sidx++) { uint8_t s = pages[i].segtable[sidx]; buf_append(&cur, pages[i].body + off, (uint32_t)s); off += (uint32_t)s; if (s < 255) { if (cur.len >= 16 && cur.p[0] == 0x01 && cur.p[1]=='v' && cur.p[2]=='o' && cur.p[3]=='r' && cur.p[4]=='b' && cur.p[5]=='i' && cur.p[6]=='s') { uint32_t sr = rd32le(cur.p + 12); buf_free(&cur); return sr; } buf_free(&cur); return 0; } } } buf_free(&cur); return 0; } static uint32_t vorbis_header_end_page_index(const OggPage* pages, uint32_t count) { uint32_t pkt_count, i, sidx; pkt_count = 0; for (i = 0; i < count; i++) { for (sidx = 0; sidx < pages[i].segtable_len; sidx++) { if (pages[i].segtable[sidx] < 255) { pkt_count++; if (pkt_count >= 3) return i; } } } return (count ? (count - 1) : 0); } /* ---------- DV!! extraction ---------- */ static void dvbang_extract(const uint8_t* dv, uint32_t dv_len, uint32_t* out_w, uint32_t* out_h, uint8_t** out_video, uint32_t* out_video_len, uint8_t** out_audio, uint32_t* out_audio_len, uint32_t* out_packet_count) { uint32_t hdr[19]; uint32_t i; uint32_t N, M; uint32_t table_off, table_len, data_off; uint32_t pos; Buf video, audio; if (dv_len < 76) die("Input too small for DV!! header"); for (i = 0; i < 19; i++) hdr[i] = rd32le(dv + i*4); if (hdr[0] != MAGIC_DV_LE) die("Bad DV!! signature"); *out_w = hdr[4]; *out_h = hdr[5]; N = hdr[7]; M = hdr[9]; *out_packet_count = N; table_off = 76; table_len = N * 20UL; if (dv_len < table_off + table_len) die("Truncated: packet table"); data_off = table_off + table_len; if (dv_len < data_off + M) die("Truncated: preroll region"); buf_init(&video); buf_init(&audio); /* audio begins with preroll */ buf_append(&audio, dv + data_off, M); pos = data_off + M; for (i = 0; i < N; i++) { const uint8_t* ent; uint32_t size_flags, size, audio_bytes; ent = dv + (table_off + i*20UL); size_flags = rd32le(ent); size = size_flags & 0x7FFFFFFFUL; audio_bytes = rd32le(ent + 12); if (pos + size > dv_len) die("Truncated: packet region"); if (audio_bytes > size) die("Corrupt: audio_bytes > size"); if (audio_bytes) { uint32_t vlen = size - audio_bytes; buf_append(&video, dv + pos, vlen); buf_append(&audio, dv + pos + vlen, audio_bytes); } else { buf_append(&video, dv + pos, size); } pos += size; } *out_video = video.p; *out_video_len = video.len; *out_audio = audio.p; *out_audio_len = audio.len; } /* ---------- MPEG-4 Part 2 frame splitting ---------- */ static Span* mpeg4_split_vop_frames(const uint8_t* es, uint32_t es_len, uint32_t* out_count) { const uint8_t vop0 = 0x00, vop1 = 0x00, vop2 = 0x01, vop3 = 0xB6; uint32_t* idxs; uint32_t cap, count, i; Span* frames; uint32_t header_len; uint8_t* header; cap = 1024; count = 0; idxs = (uint32_t*)malloc(cap * sizeof(uint32_t)); if (!idxs) die("Out of memory"); i = 0; while (i + 4 <= es_len) { uint32_t j; int found; found = 0; for (j = i; j + 4 <= es_len; j++) { if (es[j]==vop0 && es[j+1]==vop1 && es[j+2]==vop2 && es[j+3]==vop3) { found = 1; break; } } if (!found) break; if (count >= cap) { cap = cap + cap/2 + 256; idxs = (uint32_t*)realloc(idxs, cap * sizeof(uint32_t)); if (!idxs) die("Out of memory"); } idxs[count++] = j; i = j + 4; } if (count == 0) { free(idxs); die("No MPEG-4 VOP start codes (00 00 01 B6) found"); } header_len = idxs[0]; header = 0; if (header_len) { header = (uint8_t*)malloc(header_len); if (!header) die("Out of memory"); memcpy(header, es, header_len); } frames = (Span*)calloc(count, sizeof(Span)); if (!frames) die("Out of memory"); for (i = 0; i < count; i++) { uint32_t start, end, flen; start = idxs[i]; end = (i + 1 < count) ? idxs[i+1] : es_len; flen = end - start; if (i == 0) { uint32_t total = header_len + flen; frames[i].p = (uint8_t*)malloc(total); if (!frames[i].p) die("Out of memory"); if (header_len) memcpy(frames[i].p, header, header_len); memcpy(frames[i].p + header_len, es + start, flen); frames[i].len = total; } else { frames[i].p = (uint8_t*)malloc(flen); if (!frames[i].p) die("Out of memory"); memcpy(frames[i].p, es + start, flen); frames[i].len = flen; } } if (header) free(header); free(idxs); *out_count = count; return frames; } static void free_frames(Span* frames, uint32_t count) { uint32_t i; for (i = 0; i < count; i++) if (frames[i].p) free(frames[i].p); free(frames); } /* ---------- OGM packet builders ---------- */ static void ogm_build_stream_header_video(uint8_t out52[52], int w, int h, int fps) { /* See ogmtools ogmstreams.h layout; kept identical to prior variants. */ double tu_d; int64_t time_unit; int64_t samples_per_unit; uint32_t default_len; uint32_t buffersize; uint16_t bits_per_sample; uint16_t padding; memset(out52, 0, 52); memcpy(out52 + 0, "video", 5); memcpy(out52 + 8, "XVID", 4); wr32le(out52 + 12, 52); tu_d = 10000000.0 / (double)fps; time_unit = (int64_t)(tu_d + 0.5); samples_per_unit = 1; default_len = 1; buffersize = 0; bits_per_sample = 0; padding = 0; wr64le(out52 + 16, (uint64_t)time_unit); wr64le(out52 + 24, (uint64_t)samples_per_unit); wr32le(out52 + 32, default_len); wr32le(out52 + 36, buffersize); out52[40] = (uint8_t)(bits_per_sample & 0xFF); out52[41] = (uint8_t)((bits_per_sample >> 8) & 0xFF); wr32le(out52 + 42, (uint32_t)w); wr32le(out52 + 46, (uint32_t)h); out52[50] = (uint8_t)(padding & 0xFF); out52[51] = (uint8_t)((padding >> 8) & 0xFF); } static void ogm_make_video_header_pkt(Buf* pkt, int w, int h, int fps) { uint8_t sh[52]; buf_init(pkt); ogm_build_stream_header_video(sh, w, h, fps); buf_append_u8(pkt, 0x01); buf_append(pkt, sh, 52); } static void ogm_make_video_comment_pkt(Buf* pkt) { const char* vendor = "dvbang"; uint32_t vlen; uint8_t tmp[4]; vlen = (uint32_t)strlen(vendor); buf_init(pkt); buf_append_u8(pkt, 0x03); wr32le(tmp, vlen); buf_append(pkt, tmp, 4); buf_append(pkt, (const uint8_t*)vendor, vlen); wr32le(tmp, 0); buf_append(pkt, tmp, 4); } static void ogm_make_video_data_pkt(Buf* pkt, const uint8_t* frame, uint32_t frame_len) { uint8_t flags; buf_init(pkt); /* lenbytes=2 (bit1 set => 0x80), keyframe=1 (0x08) */ flags = (uint8_t)(0x80 | 0x08); buf_append_u8(pkt, flags); buf_append_u8(pkt, 1); /* duration samples=1 (LE16) */ buf_append_u8(pkt, 0); buf_append(pkt, frame, frame_len); } /* ---------- Mux (writes .ogv) ---------- */ static void mux_ogv(const char* out_path, const OggPage* audio_pages, uint32_t audio_page_count, uint32_t sample_rate, int w, int h, int fps, const Span* frames, uint32_t frame_count) { FILE* fo; Buf out; uint32_t audio_serial, video_serial, hdr_end; uint32_t vseq, v_i; double fps_d, half_frame; Buf vh, vc, vp; const uint8_t* pkts1[1]; uint32_t lens1[1]; uint32_t ai; if (audio_page_count == 0) die("No audio pages"); audio_serial = audio_pages[0].serial; video_serial = VIDEO_SERIAL_FIXED; if (video_serial == audio_serial) video_serial ^= 0xFFFFFFFFUL; hdr_end = vorbis_header_end_page_index(audio_pages, audio_page_count); buf_init(&out); /* audio BOS copied */ buf_append(&out, audio_pages[0].bytes, audio_pages[0].bytes_len); /* video BOS */ ogm_make_video_header_pkt(&vh, w, h, fps); pkts1[0] = vh.p; lens1[0] = vh.len; ogg_build_page(&out, pkts1, lens1, 1, video_serial, 0, 0, 0x02); buf_free(&vh); vseq = 1; /* video comment */ ogm_make_video_comment_pkt(&vc); pkts1[0] = vc.p; lens1[0] = vc.len; ogg_build_page(&out, pkts1, lens1, 1, video_serial, vseq++, 0, 0x00); buf_free(&vc); /* remaining audio header pages unchanged */ for (ai = 1; ai <= hdr_end && ai < audio_page_count; ai++) { buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len); } fps_d = (double)fps; half_frame = 0.5 / fps_d; v_i = 0; /* interleave */ for (ai = hdr_end + 1; ai < audio_page_count; ai++) { double at; at = 0.0; if (sample_rate) at = (double)audio_pages[ai].granule / (double)sample_rate; while (v_i < frame_count) { double vt_next; vt_next = (double)(v_i + 1) / fps_d; if (vt_next <= at + half_frame) { uint64_t gran; uint8_t header_type; ogm_make_video_data_pkt(&vp, frames[v_i].p, frames[v_i].len); pkts1[0] = vp.p; lens1[0] = vp.len; gran = (uint64_t)(v_i + 1); header_type = (uint8_t)((v_i + 1 == frame_count) ? 0x04 : 0x00); ogg_build_page(&out, pkts1, lens1, 1, video_serial, vseq++, gran, header_type); buf_free(&vp); v_i++; } else { break; } } /* copy audio page */ buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len); } /* flush remaining video */ while (v_i < frame_count) { uint64_t gran; uint8_t header_type; ogm_make_video_data_pkt(&vp, frames[v_i].p, frames[v_i].len); pkts1[0] = vp.p; lens1[0] = vp.len; gran = (uint64_t)(v_i + 1); header_type = (uint8_t)((v_i + 1 == frame_count) ? 0x04 : 0x00); ogg_build_page(&out, pkts1, lens1, 1, video_serial, vseq++, gran, header_type); buf_free(&vp); v_i++; } /* write output */ fo = fopen(out_path, "wb"); if (!fo) die("Cannot open output file"); if (out.len && fwrite(out.p, 1, out.len, fo) != out.len) { fclose(fo); die("Write failed"); } fclose(fo); buf_free(&out); } /* ---------- CLI helpers ---------- */ static int streq(const char* a, const char* b ) { return strcmp(a,b ) == 0; } static void usage(const char* exe) { printf( "Usage:\n %s <input.dv> [output.ogv] [--validate]\n\n" "Options:\n" " --validate run ffmpeg decode test (optional; ffmpeg must be in PATH)\n" " -h,--help show help\n", exe ); } static void make_default_out(const char* in, char* out, uint32_t outsz) { const char* dot; uint32_t n; dot = strrchr(in, '.'); if (!dot) { snprintf(out, outsz, "%s.ogv", in); return; } n = (uint32_t)(dot - in); if (n + 4 + 1 >= outsz) die("Output path too long"); memcpy(out, in, n); memcpy(out + n, ".ogv", 5); } int main(int argc, char** argv) { const char* in_path; const char* out_arg; char out_path[1024]; int validate; int fps; uint8_t* dv; uint32_t dv_len; uint32_t w, h, packet_count; uint8_t* video_es; uint32_t video_es_len; uint8_t* audio_ogg; uint32_t audio_ogg_len; OggPage* audio_pages; uint32_t audio_page_count; uint32_t sample_rate; Span* frames; uint32_t frame_count; int i; int positional; /* defaults */ in_path = 0; out_arg = 0; validate = 0; fps = 25; /* fixed as in your tested files */ if (argc < 2) { usage(argv[0]); return 2; } positional = 0; for (i = 1; i < argc; i++) { const char* a = argv[i]; if (streq(a, "-h") || streq(a, "--help")) { usage(argv[0]); return 0; } if (streq(a, "--validate")) { validate = 1; continue; } if (a[0] == '-') { fprintf(stderr, "Unknown option: %s\n\n", a); usage(argv[0]); return 2; } if (positional == 0) { in_path = a; positional++; continue; } if (positional == 1) { out_arg = a; positional++; continue; } fprintf(stderr, "Too many arguments.\n\n"); usage(argv[0]); return 2; } if (!in_path) { usage(argv[0]); return 2; } if (out_arg) { strncpy(out_path, out_arg, sizeof(out_path) - 1); out_path[sizeof(out_path) - 1] = 0; } else { make_default_out(in_path, out_path, (uint32_t)sizeof(out_path)); } crc_table_init(); dv = read_file_all(in_path, &dv_len); if (!dv) die2("Cannot read input file: ", in_path); /* Extract */ w = h = packet_count = 0; video_es = 0; video_es_len = 0; audio_ogg = 0; audio_ogg_len = 0; dvbang_extract(dv, dv_len, &w, &h, &video_es, &video_es_len, &audio_ogg, &audio_ogg_len, &packet_count); /* Parse audio pages */ audio_pages = 0; audio_page_count = 0; audio_pages = ogg_parse_pages(audio_ogg, audio_ogg_len, &audio_page_count); if (!audio_pages || audio_page_count == 0) die("Audio parse failed: no Ogg pages"); /* Sample rate */ sample_rate = vorbis_detect_sample_rate(audio_pages, audio_page_count); if (sample_rate == 0) { fprintf(stderr, "WARNING: could not detect Vorbis sample rate; assuming 44100.\n"); sample_rate = 44100; } /* Split frames */ frames = mpeg4_split_vop_frames(video_es, video_es_len, &frame_count); /* Mux */ mux_ogv(out_path, audio_pages, audio_page_count, sample_rate, (int)w, (int)h, fps, frames, frame_count); printf("ogv: %s\n", out_path); printf("video: MPEG-4 Part 2 (%ux%u, frames=%u, fps=%d)\n", w, h, frame_count, fps); printf("audio: Vorbis (sr=%u, pages=%u)\n", sample_rate, audio_page_count); /* Optional validation */ if (validate) { char cmd[1400]; int rc; snprintf(cmd, sizeof(cmd), "ffmpeg -loglevel error -i \"%s\" -f null -", out_path); rc = system(cmd); if (rc != 0) { fprintf(stderr, "WARNING: ffmpeg validation failed (ffmpeg missing or decode errors).\n"); } } /* cleanup */ if (dv) free(dv); if (video_es) free(video_es); if (audio_ogg) free(audio_ogg); if (audio_pages) ogg_free_pages(audio_pages, audio_page_count); if (frames) free_frames(frames, frame_count); return 0; } Спасибо сказали:
|
| -=CHE@TER=- |
Mar 3 2026, 23:34
Сообщение
#3
|
|
Walter Sullivan ![]() ![]() ![]() Группа: Root Admin Сообщений: 1,428 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 327 раз(а) |
Всё откладывается ещё на день, потому что я спать хочу.
Но я разобрался что там за странные данные были между звуком и видео, пришлось, правда, опять в дизассемблер засовывать "Angel.exe". Формат .DV файла такой: CODE #pragma pack(push, 1) typedef struct { uint32_t magic; /* "DV!!" */ uint32_t zero1; uint32_t frames1; /* unused */ uint32_t unknown; /* ??? */ uint32_t width; /* video width */ uint32_t height; /* video height */ uint32_t zero2; uint32_t frames2; /* how much frames in video file*/ uint32_t channels; /* 0 - no sound; 2 - stereo */ uint32_t asize1st; /* audio data size preroll */ uint32_t zero3[9]; } dvf_head; /* sizeof(dvf_head) == 0x4C */ typedef struct { uint32_t fsize; /* whole block size, if highest bit set (0x80000000) it's a key-frame */ uint32_t vsize; /* video frame size in this block (seems to be always valid) */ uint32_t zero1; uint32_t asize; /* audio data size (sometimes invalid!) */ uint32_t zero2; } dvf_item; /* sizeof(dvf_item) == 0x14 */ #pragma pack(pop) В файле всё это безобразие записано следующим образом: dvf_head head; // заголовок dvf_item list[head.frames2]; // таблица кадров uint8_t preroll_audio[0xC000]; // !!! при этом используются только первые "head.asize1st" байт - остальное забито нулями и должно быть пропущено! если head.channel равно нулю, то этого блока нет! Иными словами, если этот preroll_audio блок правильно прочитать, то не нужно будет искать начало видеоданных - все размеры vsize из таблицы будут точно соответствовать размеру кадра. И только после этого уже идут данные для каждого из "head.frames2" кадров. |
| -=CHE@TER=- |
Mar 4 2026, 23:30
Сообщение
#4
|
|
Walter Sullivan ![]() ![]() ![]() Группа: Root Admin Сообщений: 1,428 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 327 раз(а) |
Я, кажется, понял в чём там прикол у fsize/vsize/asize.
Похоже что vsize всегда верный, а вот asize нет. Если (fsize & 0x7FFFFFFF) равно vsize, значит, на блок для звука места нет, даже если asize не ноль! Если head.channels == 0, то звука в файле нет (в "Ангеле желаний" есть файлы без звука), в том числе нет preroll_audio блока в 0xC000 байт (head.asize1st тогда равен -1). CODE /* proper block size */ padding = list[i].fsize & 0x7FFFFFFF; /* video size seems to be always valid */ padding -= list[i].vsize; /* trim audio packet size */ list[i].asize = (list[i].asize <= padding) ? list[i].asize : padding; /* use remaining bytes as padding */ padding -= list[i].asize; /* read data */ read(hfile, video_buffer, list[i].vsize); read(hfile, video_buffer, list[i].asize); lseek(hfile, padding, SEEK_CUR); Почти всё починил, но на отдельных кадрах изображение "сыпется", хотя если звук и видео отдельно достать и проиграть, то ошибок нет. Буду дальше разбираться уже завтра. |
| Lumis |
Mar 5 2026, 00:17
Сообщение
#5
|
|
Member ![]() ![]() Группа: Authorized Сообщений: 11 Регистрация: 2-March 26 Пользователь №: 18,038 Спасибо сказали: 11 раз(а) |
Но я разобрался что там за странные данные были между звуком и видео, пришлось, правда, опять в дизассемблер засовывать "Angel.exe". У меня надежной конвертации не получалось, опять пришлось добавлять вилки логики. Иначе ряд файлов ГЭГ 2 не брало, в то время как Ангел желаний стал лучше. Причина была в том, что в таких файлах dvf_item.dsize точно равнялся размеру аудио, т.е. dsize == (total - vsize). См. dv_extract (Mode A/B). Если же добавить ифов, то конвертация стала достаточно надежной. Прошу посмотреть и покритиковать "v1.1". Стандартные типы и прочее аккуратно верну когда добьемся приемлемого качества конвертации. CODE /* dvbang2ogv.c * ANSI C / C89, Visual Studio 2008 friendly * License: MIT, Copyright 2026 Lumis * v1.1 fixes "Too many segments in one Ogg page" and many messages in ffmpeg. * * Purpose * ------- * Convert proprietary "DV!!" container (often stored as .dv or even .avi) into .ogv (OGM). * - Video stays byte-exact: MPEG-4 Part 2 elementary stream (VOP start codes 00 00 01 B6). * - Audio stays byte-exact: Ogg/Vorbis pages are copied (no re-encode). * - Muxing is done in-code (no ffmpeg muxing), and large packets are split safely * across multiple Ogg pages. * * DV!! file format (Angel.exe RE) * ------------------------------ * Header dvf_head is 0x4C bytes (19 x u32 LE): * magic "DV!!", width, height, frames2 (count), channels, asize1st (preroll used bytes), ... * * Then: * dvf_item list[frames2]; // 0x14 bytes each (5 x u32 LE) * fsize : total size (video+audio(+blob)) with key bit 0x80000000 * vsize : video size * zero1 * dsize : ambiguous (depends on file variant) * zero2 * * Then: * preroll block of 0xC000 bytes, but only first asize1st bytes are real Vorbis data; * remaining zeros must be skipped. * * After preroll block: * frame data for i in [0..frames2-1]: * video[vsize] * (optional) blob[dsize] // only in Mode B * audio[asize] * * The critical ambiguity: * Some files use dsize as "unknown blob size" between video and audio (Mode B). * Other files use dsize as "audio size" (Mode A), i.e. dsize == total - vsize. * * We detect Mode A with a robust heuristic: * For the first up to 64 entries, count matches where dsize == (total - vsize). * If >= 80% match -> Mode A, else Mode B. * * OGM mux notes * ------------- * - Copy audio BOS page unchanged * - Write video BOS packet (0x01 + OGM stream_header, subtype "XVID") * - Write video comment packet (0x03 + vendor comment) * - Copy remaining Vorbis header pages unchanged (until 3 Vorbis packets end) * - Interleave video frames against audio granulepos clock: * at = audio_granule / sample_rate * emit video frames while ((v_i+1)/fps) <= at + (0.5/fps) * then copy one audio page * Finally flush remaining video. * * Determinism * ----------- * - Video stream serial is fixed constant 0xC0DEC0DE. * - If it equals audio serial, xor with 0xFFFFFFFF (still deterministic). * * CLI * --- * dvbang2ogv <input> [output.ogv] [--fps 25] [--validate] * --validate runs: ffmpeg -loglevel error -i out.ogv -f null - * (optional, disabled by default). */ /* VC2008 doesn't have stdint.h reliably; define fixed-width types ourselves. */ typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; typedef unsigned __int64 u64; #include <stdio.h> #include <stdlib.h> #include <string.h> #ifndef snprintf #define snprintf _snprintf #endif #define MAGIC_DV_LE 0x21215644UL /* "DV!!" little-endian */ #define PREROLL_BLOCK_SIZE 0xC000UL #define VIDEO_SERIAL_FIXED 0xC0DEC0DEUL /* ---------- small utility: fatal errors ---------- */ static void die(const char* msg) { fprintf(stderr, "ERROR: %s\n", msg); exit(2); } static void die2(const char* msg, const char* more) { fprintf(stderr, "ERROR: %s%s\n", msg, more ? more : ""); exit(2); } /* ---------- LE helpers (byte safe, no alignment assumptions) ---------- */ static u32 rd32le(const u8* p) { return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24); } static u64 rd64le(const u8* p) { u64 v; v = (u64)p[0]; v |= (u64)p[1] << 8; v |= (u64)p[2] << 16; v |= (u64)p[3] << 24; v |= (u64)p[4] << 32; v |= (u64)p[5] << 40; v |= (u64)p[6] << 48; v |= (u64)p[7] << 56; return v; } static void wr32le(u8* p, u32 v) { p[0] = (u8)(v & 0xFF); p[1] = (u8)((v >> 8) & 0xFF); p[2] = (u8)((v >> 16) & 0xFF); p[3] = (u8)((v >> 24) & 0xFF); } static void wr64le(u8* p, u64 v) { p[0] = (u8)(v & 0xFF); p[1] = (u8)((v >> 8) & 0xFF); p[2] = (u8)((v >> 16) & 0xFF); p[3] = (u8)((v >> 24) & 0xFF); p[4] = (u8)((v >> 32) & 0xFF); p[5] = (u8)((v >> 40) & 0xFF); p[6] = (u8)((v >> 48) & 0xFF); p[7] = (u8)((v >> 56) & 0xFF); } /* ---------- growable byte buffer ---------- */ typedef struct Buf { u8* p; u32 len; u32 cap; } Buf; static void buf_init(Buf* B) { b->p = 0; b->len = 0; b->cap = 0; } static void buf_reserve(Buf* b, u32 need) { u32 ncap; u8* np; if (need <= b->cap) return; ncap = b->cap ? (b->cap + (b->cap >> 1) + 64) : 256; if (ncap < need) ncap = need; np = (u8*)realloc(b->p, (size_t)ncap); if (!np) die("Out of memory"); b->p = np; b->cap = ncap; } static void buf_append(Buf* b, const u8* data, u32 n) { if (n == 0) return; buf_reserve(b, b->len + n); memcpy(b->p + b->len, data, (size_t)n); b->len += n; } static void buf_append_u8(Buf* b, u8 v) { buf_reserve(b, b->len + 1); b->p[b->len++] = v; } static void buf_free(Buf* B) { if (b->p) free(b->p); b->p = 0; b->len = 0; b->cap = 0; } /* ---------- file read all ---------- */ static u8* read_file_all(const char* path, u32* out_len) { FILE* f; long sz; u8* data; size_t got; f = fopen(path, "rb"); if (!f) return 0; fseek(f, 0, SEEK_END); sz = ftell(f); if (sz < 0) { fclose(f); return 0; } fseek(f, 0, SEEK_SET); data = (u8*)malloc((size_t)sz); if (!data) { fclose(f); return 0; } got = 0; if (sz > 0) got = fread(data, 1, (size_t)sz, f); fclose(f); if ((long)got != sz) { free(data); return 0; } *out_len = (u32)sz; return data; } /* ---------- Ogg CRC32 (Ogg polynomial) ---------- */ static u32 CRC_TABLE[256]; static void crc_table_init(void) { u32 poly, i, j, r; poly = 0x04C11DB7UL; for (i = 0; i < 256; i++) { r = i << 24; for (j = 0; j < 8; j++) { if (r & 0x80000000UL) r = (r << 1) ^ poly; else r <<= 1; } CRC_TABLE[i] = r; } } static u32 ogg_crc(const u8* page, u32 len) { u32 crc, i; crc = 0; for (i = 0; i < len; i++) { u8 b = page[i]; crc = (crc << 8) ^ CRC_TABLE[((crc >> 24) ^ B) & 0xFF]; } return crc; } /* ---------- Ogg page representation (parsed audio pages only) ---------- */ typedef struct OggPage { u8 header_type; u64 granule; u32 serial; u32 seq; u8* segs; /* lacing table */ u32 segs_len; u8* body; u32 body_len; u8* raw; /* full page bytes (header+segs+body) */ u32 raw_len; } OggPage; /* Parse pages by scanning for "OggS". Minimal but sufficient for valid Vorbis streams. */ static OggPage* ogg_parse_pages(const u8* data, u32 len, u32* out_count) { OggPage* pages; u32 cap, count, i; cap = 64; count = 0; pages = (OggPage*)calloc((size_t)cap, sizeof(OggPage)); if (!pages) die("Out of memory"); i = 0; while (1) { u32 j, k, body_len, seg_off, body_off, end; int found; u8 nseg; found = 0; for (j = i; j + 4 <= len; j++) { if (data[j]=='O' && data[j+1]=='g' && data[j+2]=='g' && data[j+3]=='S') { found = 1; break; } } if (!found) break; if (j + 27 > len) break; nseg = data[j + 26]; seg_off = j + 27; body_off = seg_off + (u32)nseg; if (body_off > len) break; body_len = 0; for (k = 0; k < (u32)nseg; k++) body_len += data[seg_off + k]; end = body_off + body_len; if (end > len) break; if (count >= cap) { OggPage* np; cap = cap + cap/2 + 32; np = (OggPage*)realloc(pages, (size_t)cap * sizeof(OggPage)); if (!np) die("Out of memory"); pages = np; memset(pages + count, 0, (size_t)(cap - count) * sizeof(OggPage)); } pages[count].header_type = data[j + 5]; pages[count].granule = rd64le(data + j + 6); pages[count].serial = rd32le(data + j + 14); pages[count].seq = rd32le(data + j + 18); pages[count].segs_len = (u32)nseg; pages[count].segs = (u8*)malloc((size_t)nseg); if (!pages[count].segs) die("Out of memory"); memcpy(pages[count].segs, data + seg_off, (size_t)nseg); pages[count].body_len = body_len; pages[count].body = (u8*)malloc((size_t)body_len); if (!pages[count].body) die("Out of memory"); memcpy(pages[count].body, data + body_off, (size_t)body_len); pages[count].raw_len = end - j; pages[count].raw = (u8*)malloc((size_t)pages[count].raw_len); if (!pages[count].raw) die("Out of memory"); memcpy(pages[count].raw, data + j, (size_t)pages[count].raw_len); count++; i = end; } *out_count = count; return pages; } static void ogg_free_pages(OggPage* pages, u32 count) { u32 i; if (!pages) return; for (i = 0; i < count; i++) { if (pages[i].segs) free(pages[i].segs); if (pages[i].body) free(pages[i].body); if (pages[i].raw) free(pages[i].raw); } free(pages); } /* Extract Vorbis sample rate from identification header packet (0x01 'vorbis'). */ static u32 vorbis_detect_sample_rate(const OggPage* pages, u32 count) { Buf cur; u32 i, sidx; buf_init(&cur); for (i = 0; i < count; i++) { u32 off; off = 0; for (sidx = 0; sidx < pages[i].segs_len; sidx++) { u8 s = pages[i].segs[sidx]; buf_append(&cur, pages[i].body + off, (u32)s); off += (u32)s; if (s < 255) { if (cur.len >= 16 && cur.p[0] == 0x01 && cur.p[1]=='v' && cur.p[2]=='o' && cur.p[3]=='r' && cur.p[4]=='b' && cur.p[5]=='i' && cur.p[6]=='s') { u32 sr = rd32le(cur.p + 12); buf_free(&cur); return sr; } cur.len = 0; /* reset packet */ } } } buf_free(&cur); return 0; } /* Return page index where the 3rd Vorbis header packet ends (ident, comment, setup). */ static u32 vorbis_header_end_page_index(const OggPage* pages, u32 count) { u32 pkt, i, sidx; pkt = 0; for (i = 0; i < count; i++) { for (sidx = 0; sidx < pages[i].segs_len; sidx++) { if (pages[i].segs[sidx] < 255) { pkt++; if (pkt >= 3) return i; } } } return (count ? (count - 1) : 0); } /* ---------- Ogg page builder (raw) ---------- * Build one page from provided lacing table + body, and append to output buffer. */ static void ogg_build_page_raw(Buf* out, const u8* segs, u32 seg_count, const u8* body, u32 body_len, u32 serial, u32 seq, u64 granule, u8 header_type) { Buf page; u8 hdr[27]; u32 crc; buf_init(&page); memset(hdr, 0, sizeof(hdr)); hdr[0] = 'O'; hdr[1] = 'g'; hdr[2] = 'g'; hdr[3] = 'S'; hdr[4] = 0; hdr[5] = header_type; wr64le(hdr + 6, granule); wr32le(hdr + 14, serial); wr32le(hdr + 18, seq); wr32le(hdr + 22, 0); hdr[26] = (u8)seg_count; buf_append(&page, hdr, 27); buf_append(&page, segs, seg_count); buf_append(&page, body, body_len); crc = ogg_crc(page.p, page.len); wr32le(page.p + 22, crc); buf_append(out, page.p, page.len); buf_free(&page); } /* ---------- Ogg packet writer with splitting ---------- * Writes ONE logical packet, splitting across multiple pages if packet lacing would exceed 255 segments. * * Continuation logic: * - For non-first fragments: header_type |= 0x01 (continued) * - All non-last fragments end with lacing value 255 (packet continues) * - Granulepos is written ONLY on the last fragment (standard practice) */ static void ogg_write_packet_split(Buf* out, const u8* pkt, u32 pkt_len, u32 serial, u32* io_seq, u64 granule_last, u8 header_type_first, u8 header_type_last) { u32 pos; u32 seq; u32 max_body; pos = 0; seq = *io_seq; max_body = 255UL * 255UL; /* 65025 */ while (pos < pkt_len) { u32 remaining; remaining = pkt_len - pos; if (remaining <= max_body) { /* last (or only) fragment */ u8 segs[255]; u32 seg_count; u32 n; u8 ht; seg_count = 0; n = remaining; while (n >= 255) { segs[seg_count++] = 255; n -= 255; } segs[seg_count++] = (u8)n; /* may be 0 if exact multiple */ if (pos == 0) ht = (u8)(header_type_first | header_type_last); else ht = (u8)(0x01 | header_type_last); ogg_build_page_raw(out, segs, seg_count, pkt + pos, remaining, serial, seq, granule_last, ht); seq++; break; } else { /* middle fragment: exactly max_body bytes, 255 segments of 255 each */ u8 segs_full[255]; u32 i; u8 ht; for (i = 0; i < 255; i++) segs_full[i] = 255; ht = (pos == 0) ? header_type_first : 0x01; ogg_build_page_raw(out, segs_full, 255, pkt + pos, max_body, serial, seq, 0 /* granule only on last */, ht); seq++; pos += max_body; } } *io_seq = seq; } /* ---------- MPEG-4 Part 2 frame splitting ---------- * Split ES by VOP start code 00 00 01 B6. * The bytes before the first VOP (VOL/VOS) are prepended to the first frame. */ typedef struct Span { u8* p; u32 len; } Span; static Span* mpeg4_split_vop_frames(const u8* es, u32 es_len, u32* out_count) { u32* idxs; u32 cap, count, i; Span* frames; u32 header_len; u8* header; cap = 1024; count = 0; idxs = (u32*)malloc((size_t)cap * sizeof(u32)); if (!idxs) die("Out of memory"); i = 0; while (i + 4 <= es_len) { u32 j; int found; found = 0; for (j = i; j + 4 <= es_len; j++) { if (es[j]==0x00 && es[j+1]==0x00 && es[j+2]==0x01 && es[j+3]==0xB6) { found = 1; break; } } if (!found) break; if (count >= cap) { u32* np; cap = cap + cap/2 + 256; np = (u32*)realloc(idxs, (size_t)cap * sizeof(u32)); if (!np) die("Out of memory"); idxs = np; } idxs[count++] = j; i = j + 4; } if (count == 0) { free(idxs); die("No MPEG-4 VOP start codes (00 00 01 B6) found"); } header_len = idxs[0]; header = 0; if (header_len) { header = (u8*)malloc((size_t)header_len); if (!header) die("Out of memory"); memcpy(header, es, (size_t)header_len); } frames = (Span*)calloc((size_t)count, sizeof(Span)); if (!frames) die("Out of memory"); for (i = 0; i < count; i++) { u32 start, end, flen; start = idxs[i]; end = (i + 1 < count) ? idxs[i+1] : es_len; flen = end - start; if (i == 0 && header_len) { u32 total; total = header_len + flen; frames[i].p = (u8*)malloc((size_t)total); if (!frames[i].p) die("Out of memory"); memcpy(frames[i].p, header, (size_t)header_len); memcpy(frames[i].p + header_len, es + start, (size_t)flen); frames[i].len = total; } else { frames[i].p = (u8*)malloc((size_t)flen); if (!frames[i].p) die("Out of memory"); memcpy(frames[i].p, es + start, (size_t)flen); frames[i].len = flen; } } if (header) free(header); free(idxs); *out_count = count; return frames; } static void free_frames(Span* frames, u32 count) { u32 i; if (!frames) return; for (i = 0; i < count; i++) if (frames[i].p) free(frames[i].p); free(frames); } /* ---------- DV extraction: fixed heuristic (Mode A / Mode B) ---------- */ typedef struct FrameMeta { u8 key; } FrameMeta; static void dv_extract(const u8* dv, u32 dv_len, u32* out_w, u32* out_h, u32* out_frames, u32* out_channels, u8** out_video_es, u32* out_video_es_len, u8** out_audio_ogg, u32* out_audio_ogg_len, FrameMeta** out_meta, int* out_mode_a) { u32 magic, w, h, frames2, channels, asize1st; u32 table_off, table_len, preroll_off, data_off; u32 probe, matches; int mode_a; Buf video, audio; FrameMeta* meta; if (dv_len < 0x4C) die("Input too small for dvf_head"); magic = rd32le(dv + 0); if (magic != MAGIC_DV_LE) die("Bad DV!! signature"); w = rd32le(dv + 16); h = rd32le(dv + 20); frames2 = rd32le(dv + 28); channels = rd32le(dv + 32); asize1st = rd32le(dv + 36); table_off = 0x4C; table_len = frames2 * 0x14UL; preroll_off = table_off + table_len; if (dv_len < preroll_off + PREROLL_BLOCK_SIZE) die("Truncated preroll block"); if (asize1st > PREROLL_BLOCK_SIZE) die("Corrupt asize1st > 0xC000"); /* Heuristic: Mode A if dsize == total - vsize for >=80% of first up to 64 items. */ probe = (frames2 < 64) ? frames2 : 64; matches = 0; if (probe > 0) { u32 i; for (i = 0; i < probe; i++) { u32 base, fsize, vsize, dsize, total; base = table_off + i * 0x14UL; fsize = rd32le(dv + base + 0); vsize = rd32le(dv + base + 4); dsize = rd32le(dv + base + 12); total = (fsize & 0x7FFFFFFFUL); if (total >= vsize && dsize == (total - vsize)) matches++; } } mode_a = (probe > 0 && (matches * 100U) >= (probe * 80U)) ? 1 : 0; buf_init(&video); buf_init(&audio); /* Audio begins with preroll used bytes; remaining zeros are skipped. */ buf_append(&audio, dv + preroll_off, asize1st); data_off = preroll_off + PREROLL_BLOCK_SIZE; meta = (FrameMeta*)calloc((size_t)frames2, sizeof(FrameMeta)); if (!meta) die("Out of memory"); { u32 pos, i; pos = data_off; for (i = 0; i < frames2; i++) { u32 base, fsize, vsize, dsize, total; u32 blob, a_bytes, need; u8 is_key; base = table_off + i * 0x14UL; fsize = rd32le(dv + base + 0); vsize = rd32le(dv + base + 4); dsize = rd32le(dv + base + 12); total = (fsize & 0x7FFFFFFFUL); is_key = (u8)((fsize & 0x80000000UL) ? 1 : 0); meta[i].key = is_key; if (total < vsize) die("Corrupt item: total < vsize"); if (mode_a) { blob = 0; a_bytes = total - vsize; } else { blob = dsize; if (blob > (total - vsize)) die("Corrupt item: blob too big"); a_bytes = total - vsize - blob; } need = vsize + blob + a_bytes; if (pos + need > dv_len) die("Truncated: frame data"); /* video */ buf_append(&video, dv + pos, vsize); pos += vsize; /* blob (Mode B) */ pos += blob; /* audio */ if (a_bytes) buf_append(&audio, dv + pos, a_bytes); pos += a_bytes; } } *out_w = w; *out_h = h; *out_frames = frames2; *out_channels = channels; *out_video_es = video.p; *out_video_es_len = video.len; *out_audio_ogg = audio.p; *out_audio_ogg_len = audio.len; *out_meta = meta; *out_mode_a = mode_a; } /* ---------- OGM packet builders ---------- */ static void ogm_build_stream_header_video(u8 out52[52], int w, int h, int fps) { /* Correct OGM 52-byte header layout (critical: width/height offsets). */ double tu_d; u64 time_unit; memset(out52, 0, 52); memcpy(out52 + 0, "video", 5); memcpy(out52 + 8, "XVID", 4); wr32le(out52 + 12, 52); tu_d = 10000000.0 / (double)fps; time_unit = (u64)(tu_d + 0.5); wr64le(out52 + 16, time_unit); /* time_unit */ wr64le(out52 + 24, (u64)1); /* samples_per_unit */ wr32le(out52 + 32, 1); /* default_len */ wr32le(out52 + 36, 0); /* buffersize */ out52[40] = 0; out52[41] = 0; /* bits_per_sample */ out52[42] = 0; out52[43] = 0; /* padding */ wr32le(out52 + 44, (u32)w); /* width */ wr32le(out52 + 48, (u32)h); /* height */ } static void ogm_make_video_header_pkt(Buf* pkt, int w, int h, int fps) { u8 sh[52]; buf_init(pkt); ogm_build_stream_header_video(sh, w, h, fps); buf_append_u8(pkt, 0x01); buf_append(pkt, sh, 52); } static void ogm_make_video_comment_pkt(Buf* pkt) { const char* vendor = "dvbang"; u32 vlen; u8 tmp[4]; vlen = (u32)strlen(vendor); buf_init(pkt); buf_append_u8(pkt, 0x03); wr32le(tmp, vlen); buf_append(pkt, tmp, 4); buf_append(pkt, (const u8*)vendor, vlen); wr32le(tmp, 0); buf_append(pkt, tmp, 4); } static void ogm_make_video_data_pkt(Buf* pkt, const u8* frame, u32 frame_len, u8 is_key) { u8 flags; buf_init(pkt); /* OGM video packet: flags: lenbytes=2 -> 0x80, keyframe -> 0x08 (if key) duration: 1 (LE16) */ flags = (u8)(0x80 | (is_key ? 0x08 : 0x00)); buf_append_u8(pkt, flags); buf_append_u8(pkt, 1); buf_append_u8(pkt, 0); buf_append(pkt, frame, frame_len); } /* ---------- mux .ogv ---------- */ static void mux_ogv(const char* out_path, const OggPage* audio_pages, u32 audio_page_count, u32 sample_rate, int w, int h, int fps, const Span* frames, const FrameMeta* meta, u32 frame_count) { FILE* fo; Buf out; u32 audio_serial, video_serial; u32 hdr_end; u32 vseq; u32 ai, v_i; double fps_d, half_frame; Buf pkt; if (audio_page_count == 0) die("No audio pages"); audio_serial = audio_pages[0].serial; video_serial = VIDEO_SERIAL_FIXED; if (video_serial == audio_serial) video_serial ^= 0xFFFFFFFFUL; hdr_end = vorbis_header_end_page_index(audio_pages, audio_page_count); buf_init(&out); /* Copy audio BOS unchanged */ buf_append(&out, audio_pages[0].raw, audio_pages[0].raw_len); /* Video BOS packet (split-safe even though small) */ vseq = 0; ogm_make_video_header_pkt(&pkt, w, h, fps); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (u64)0, (u8)0x02 /* BOS */, (u8)0x00); buf_free(&pkt); /* Video comment packet */ ogm_make_video_comment_pkt(&pkt); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (u64)0, (u8)0x00, (u8)0x00); buf_free(&pkt); /* Copy remaining Vorbis header pages unchanged (until 3 header packets end) */ for (ai = 1; ai <= hdr_end && ai < audio_page_count; ai++) { buf_append(&out, audio_pages[ai].raw, audio_pages[ai].raw_len); } fps_d = (double)fps; half_frame = 0.5 / fps_d; v_i = 0; /* Interleave: for each audio page after headers, emit video frames up to its timestamp */ for (ai = hdr_end + 1; ai < audio_page_count; ai++) { double at; at = 0.0; if (sample_rate) at = (double)audio_pages[ai].granule / (double)sample_rate; while (v_i < frame_count) { double vt_next; vt_next = (double)(v_i + 1) / fps_d; if (vt_next <= at + half_frame) { u8 eos; u64 gran; u8 is_key; is_key = meta ? meta[v_i].key : 1; eos = (u8)((v_i + 1 == frame_count) ? 0x04 : 0x00); gran = (u64)(v_i + 1); ogm_make_video_data_pkt(&pkt, frames[v_i].p, frames[v_i].len, is_key); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, gran, (u8)0x00, eos); buf_free(&pkt); v_i++; } else { break; } } /* Copy one audio page unchanged */ buf_append(&out, audio_pages[ai].raw, audio_pages[ai].raw_len); } /* Flush remaining video frames */ while (v_i < frame_count) { u8 eos; u64 gran; u8 is_key; is_key = meta ? meta[v_i].key : 1; eos = (u8)((v_i + 1 == frame_count) ? 0x04 : 0x00); gran = (u64)(v_i + 1); ogm_make_video_data_pkt(&pkt, frames[v_i].p, frames[v_i].len, is_key); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, gran, (u8)0x00, eos); buf_free(&pkt); v_i++; } fo = fopen(out_path, "wb"); if (!fo) die("Cannot open output file"); if (out.len && fwrite(out.p, 1, (size_t)out.len, fo) != (size_t)out.len) { fclose(fo); die("Write failed"); } fclose(fo); buf_free(&out); } /* ---------- CLI ---------- */ static int streq(const char* a, const char* B) { return strcmp(a, B) == 0; } static void usage(const char* exe) { fprintf(stderr, "Usage:\n" " %s <input> [output.ogv] [--fps N] [--validate]\n\n" "Notes:\n" " - input is detected by DV!! signature, extension doesn't matter (.dv/.avi/...)\n" " - default fps = 25\n\n" "Options:\n" " --fps N set frames per second (default 25)\n" " --validate run ffmpeg decode test (optional; requires ffmpeg in PATH)\n" " -h,--help show help\n", exe); } static void make_default_out(const char* in, char* out, u32 outsz) { const char* dot; u32 n; dot = strrchr(in, '.'); if (!dot) { snprintf(out, outsz, "%s.ogv", in); return; } n = (u32)(dot - in); if (n + 4 + 1 >= outsz) die("Output path too long"); memcpy(out, in, (size_t)n); memcpy(out + n, ".ogv", 5); } int main(int argc, char** argv) { const char* in_path; const char* out_arg; char out_path[1024]; int validate; int fps; u8* dv; u32 dv_len; u32 w, h, frames2, channels; u8* video_es; u32 video_es_len; u8* audio_ogg; u32 audio_ogg_len; FrameMeta* meta; int mode_a; OggPage* audio_pages; u32 audio_page_count; u32 sample_rate; Span* frames; u32 frame_count; int i; int positional; in_path = 0; out_arg = 0; validate = 0; fps = 25; if (argc < 2) { usage(argv[0]); return 2; } positional = 0; for (i = 1; i < argc; i++) { const char* a = argv[i]; if (streq(a, "-h") || streq(a, "--help")) { usage(argv[0]); return 0; } if (streq(a, "--validate")) { validate = 1; continue; } if (streq(a, "--fps")) { if (i + 1 >= argc) { fprintf(stderr, "Missing value for --fps\n\n"); usage(argv[0]); return 2; } fps = atoi(argv[++i]); if (fps <= 0 || fps > 240) { fprintf(stderr, "Invalid fps: %d\n\n", fps); return 2; } continue; } if (a[0] == '-') { fprintf(stderr, "Unknown option: %s\n\n", a); usage(argv[0]); return 2; } if (positional == 0) { in_path = a; positional++; continue; } if (positional == 1) { out_arg = a; positional++; continue; } fprintf(stderr, "Too many arguments.\n\n"); usage(argv[0]); return 2; } if (!in_path) { usage(argv[0]); return 2; } if (out_arg) { strncpy(out_path, out_arg, sizeof(out_path) - 1); out_path[sizeof(out_path) - 1] = 0; } else { make_default_out(in_path, out_path, (u32)sizeof(out_path)); } crc_table_init(); dv = read_file_all(in_path, &dv_len); if (!dv) die2("Cannot read input file: ", in_path); w = h = frames2 = channels = 0; video_es = 0; video_es_len = 0; audio_ogg = 0; audio_ogg_len = 0; meta = 0; mode_a = 0; dv_extract(dv, dv_len, &w, &h, &frames2, &channels, &video_es, &video_es_len, &audio_ogg, &audio_ogg_len, &meta, &mode_a); audio_pages = 0; audio_page_count = 0; audio_pages = ogg_parse_pages(audio_ogg, audio_ogg_len, &audio_page_count); if (!audio_pages || audio_page_count == 0) die("Audio parse failed: no Ogg pages found"); sample_rate = vorbis_detect_sample_rate(audio_pages, audio_page_count); if (sample_rate == 0) { fprintf(stderr, "WARNING: could not detect Vorbis sample rate; assuming 44100.\n"); sample_rate = 44100; } frames = mpeg4_split_vop_frames(video_es, video_es_len, &frame_count); if (frames2 != 0 && frame_count != frames2) { fprintf(stderr, "WARNING: frame count mismatch (table=%u, VOP=%u). Continuing.\n", (unsigned)frames2, (unsigned)frame_count); } mux_ogv(out_path, audio_pages, audio_page_count, sample_rate, (int)w, (int)h, fps, frames, meta, frame_count); printf("ogv: %s\n", out_path); printf("video: MPEG-4 Part 2 (%ux%u, frames=%u, fps=%d)\n", (unsigned)w, (unsigned)h, (unsigned)frame_count, fps); printf("audio: Vorbis (sr=%u, pages=%u, channels=%u)\n", (unsigned)sample_rate, (unsigned)audio_page_count, (unsigned)channels); printf("dv-mode: %s\n", mode_a ? "A (dsize=audio_size)" : "B (dsize=blob_size)"); if (validate) { char cmd[1400]; int rc; snprintf(cmd, sizeof(cmd), "ffmpeg -hide_banner -loglevel error -i \"%s\" -f null -", out_path); rc = system(cmd); if (rc != 0) { fprintf(stderr, "WARNING: ffmpeg validation failed (ffmpeg missing or decode errors).\n"); } } if (dv) free(dv); if (video_es) free(video_es); if (audio_ogg) free(audio_ogg); if (audio_pages) ogg_free_pages(audio_pages, audio_page_count); if (frames) free_frames(frames, frame_count); if (meta) free(meta); return 0; } Спасибо сказали:
|
| Lumis |
Mar 5 2026, 01:14
Сообщение
#6
|
|
Member ![]() ![]() Группа: Authorized Сообщений: 11 Регистрация: 2-March 26 Пользователь №: 18,038 Спасибо сказали: 11 раз(а) |
А вот версия 1.2, где получилось обработать ситуацию без звука.
CODE /* dvbang2ogv.c * ANSI C / C89, Visual Studio 2008 friendly * License: MIT, Copyright 2026 Lumis * v1.2 fixes dv without sound * * Converts proprietary "DV!!" container into .ogv (OGM). * - Video: MPEG-4 Part 2 ES (byte-exact). * - Audio: Ogg/Vorbis pages copied (byte-exact). * - Muxing in-code; large OGM packets are split across Ogg pages (no "Too many segments"). * * DV!! layout (most common): * dvf_head (0x4C) * dvf_item list[frames2] (0x14 each) * preroll_audio block of fixed 0xC000 bytes (only first asize1st bytes are meaningful) * then frame payload blocks. * * BUT: if channels==0, there is NO audio and typically NO preroll block at all: * data starts immediately after the table (data_off = preroll_off). * * Per-frame robust interpretation: * total = fsize & 0x7fffffff * vsize = item.vsize * dsize = item.dsize (meaning depends on file) * * If channels==0: treat every packet as pure video (ignore dsize), consume exactly 'total'. * * If channels!=0: * If total < vsize => corrupt. * * If total == vsize: * - Often means "video + tail-audio stored inside the same packet", with dsize = audio_len. * If dsize <= total: video_len = total - dsize; audio_len = dsize. * - Some files have short packets while dsize stays constant (e.g. 53-99.dv): * If dsize > total: treat packet as AUDIO-ONLY: video_len=0; audio_len=total. * * If total > vsize: * rem = total - vsize * - if dsize == rem: audio_len = rem (audio after video) * - if dsize < rem: blob_len = dsize, audio_len = rem - blob_len * - if dsize > rem: ambiguous. In practice for your set we must NOT jump outside 'total' * when file tail matches exactly data_off + sum(total). * So we interpret as tail-audio: if dsize <= total => video_len = total - dsize, audio_len = dsize. * else corrupt. */ typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; typedef unsigned __int64 u64; #include <stdio.h> #include <stdlib.h> #include <string.h> #ifndef snprintf #define snprintf _snprintf #endif #define MAGIC_DV_LE 0x21215644UL /* "DV!!" little-endian */ #define PREROLL_BLOCK_SIZE 0xC000UL #define VIDEO_SERIAL_FIXED 0xC0DEC0DEUL /* ---------- errors ---------- */ static void die(const char* msg) { fprintf(stderr, "ERROR: %s\n", msg); exit(2); } static void die2(const char* msg, const char* more) { fprintf(stderr, "ERROR: %s%s\n", msg, more ? more : ""); exit(2); } /* ---------- little-endian helpers ---------- */ static u32 rd32le(const u8* p) { return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24); } static u64 rd64le(const u8* p) { u64 v = 0; v |= (u64)p[0]; v |= (u64)p[1] << 8; v |= (u64)p[2] << 16; v |= (u64)p[3] << 24; v |= (u64)p[4] << 32; v |= (u64)p[5] << 40; v |= (u64)p[6] << 48; v |= (u64)p[7] << 56; return v; } static void wr32le(u8* p, u32 v) { p[0]=(u8)(v); p[1]=(u8)(v>>8); p[2]=(u8)(v>>16); p[3]=(u8)(v>>24); } static void wr64le(u8* p, u64 v) { p[0]=(u8)(v); p[1]=(u8)(v>>8); p[2]=(u8)(v>>16); p[3]=(u8)(v>>24); p[4]=(u8)(v>>32); p[5]=(u8)(v>>40); p[6]=(u8)(v>>48); p[7]=(u8)(v>>56); } /* ---------- buffer ---------- */ typedef struct Buf { u8* p; u32 len; u32 cap; } Buf; static void buf_init(Buf* static void buf_reserve(Buf* b, u32 need){ u32 ncap; u8* np; if (need <= b->cap) return; ncap = b->cap ? (b->cap + (b->cap>>1) + 64) : 256; if (ncap < need) ncap = need; np = (u8*)realloc(b->p, (size_t)ncap); if (!np) die("Out of memory"); b->p=np; b->cap=ncap; } static void buf_append(Buf* b, const u8* data, u32 n){ if (!n) return; buf_reserve(b, b->len + n); memcpy(b->p + b->len, data, (size_t)n); b->len += n; } static void buf_append_u8(Buf* b, u8 v){ buf_reserve(b, b->len + 1); b->p[b->len++] = v; } static void buf_free(Buf* /* ---------- read file ---------- */ static u8* read_file_all(const char* path, u32* out_len){ FILE* f; long sz; u8* data; size_t got; f = fopen(path, "rb"); if (!f) return 0; fseek(f, 0, SEEK_END); sz = ftell(f); if (sz < 0) { fclose(f); return 0; } fseek(f, 0, SEEK_SET); data = (u8*)malloc((size_t)sz); if (!data) { fclose(f); return 0; } got = (sz>0) ? fread(data, 1, (size_t)sz, f) : 0; fclose(f); if ((long)got != sz) { free(data); return 0; } *out_len = (u32)sz; return data; } /* ---------- ogg crc ---------- */ static u32 CRC_TABLE[256]; static void crc_table_init(void){ u32 poly=0x04C11DB7UL, i, j, r; for (i=0;i<256;i++){ r = i<<24; for (j=0;j<8;j++){ if (r & 0x80000000UL) r = (r<<1) ^ poly; else r <<= 1; } CRC_TABLE[i]=r; } } static u32 ogg_crc(const u8* page, u32 len){ u32 crc=0, i; for (i=0;i<len;i++){ u8 b=page[i]; crc = (crc<<8) ^ CRC_TABLE[((crc>>24) ^ } return crc; } /* ---------- ogg page (parsed audio only) ---------- */ typedef struct OggPage { u8 header_type; u64 granule; u32 serial; u32 seq; u8* segs; u32 segs_len; u8* body; u32 body_len; u8* raw; u32 raw_len; } OggPage; static OggPage* ogg_parse_pages(const u8* data, u32 len, u32* out_count){ OggPage* pages; u32 cap=64, count=0, i=0; pages = (OggPage*)calloc((size_t)cap, sizeof(OggPage)); if (!pages) die("Out of memory"); while (1){ u32 j,k,body_len,seg_off,body_off,end; int found; u8 nseg; found=0; for (j=i; j+4<=len; j++){ if (data[j]=='O' && data[j+1]=='g' && data[j+2]=='g' && data[j+3]=='S'){ found=1; break; } } if (!found) break; if (j+27 > len) break; nseg = data[j+26]; seg_off = j+27; body_off = seg_off + (u32)nseg; if (body_off > len) break; body_len=0; for (k=0;k<(u32)nseg;k++) body_len += data[seg_off+k]; end = body_off + body_len; if (end > len) break; if (count >= cap){ OggPage* np; cap = cap + cap/2 + 32; np = (OggPage*)realloc(pages, (size_t)cap * sizeof(OggPage)); if (!np) die("Out of memory"); pages=np; memset(pages+count, 0, (size_t)(cap-count)*sizeof(OggPage)); } pages[count].header_type = data[j+5]; pages[count].granule = rd64le(data + j + 6); pages[count].serial = rd32le(data + j + 14); pages[count].seq = rd32le(data + j + 18); pages[count].segs_len = (u32)nseg; pages[count].segs = (u8*)malloc((size_t)nseg); if (!pages[count].segs) die("Out of memory"); memcpy(pages[count].segs, data + seg_off, (size_t)nseg); pages[count].body_len = body_len; pages[count].body = (u8*)malloc((size_t)body_len); if (!pages[count].body) die("Out of memory"); memcpy(pages[count].body, data + body_off, (size_t)body_len); pages[count].raw_len = end - j; pages[count].raw = (u8*)malloc((size_t)pages[count].raw_len); if (!pages[count].raw) die("Out of memory"); memcpy(pages[count].raw, data + j, (size_t)pages[count].raw_len); count++; i = end; } *out_count = count; return pages; } static void ogg_free_pages(OggPage* pages, u32 count){ u32 i; if (!pages) return; for (i=0;i<count;i++){ if (pages[i].segs) free(pages[i].segs); if (pages[i].body) free(pages[i].body); if (pages[i].raw) free(pages[i].raw); } free(pages); } static u32 vorbis_detect_sample_rate(const OggPage* pages, u32 count){ Buf cur; u32 i,sidx; buf_init(&cur); for (i=0;i<count;i++){ u32 off=0; for (sidx=0;sidx<pages[i].segs_len;sidx++){ u8 s = pages[i].segs[sidx]; buf_append(&cur, pages[i].body + off, (u32)s); off += (u32)s; if (s < 255){ if (cur.len >= 16 && cur.p[0]==0x01 && cur.p[1]=='v' && cur.p[2]=='o' && cur.p[3]=='r' && cur.p[4]=='b' && cur.p[5]=='i' && cur.p[6]=='s') { u32 sr = rd32le(cur.p + 12); buf_free(&cur); return sr; } cur.len = 0; } } } buf_free(&cur); return 0; } static u32 vorbis_header_end_page_index(const OggPage* pages, u32 count){ u32 pkt=0,i,sidx; for (i=0;i<count;i++){ for (sidx=0;sidx<pages[i].segs_len;sidx++){ if (pages[i].segs[sidx] < 255){ pkt++; if (pkt >= 3) return i; } } } return (count ? (count-1) : 0); } /* ---------- ogg build page raw ---------- */ static void ogg_build_page_raw(Buf* out, const u8* segs, u32 seg_count, const u8* body, u32 body_len, u32 serial, u32 seq, u64 granule, u8 header_type) { Buf page; u8 hdr[27]; u32 crc; buf_init(&page); memset(hdr, 0, sizeof(hdr)); hdr[0]='O'; hdr[1]='g'; hdr[2]='g'; hdr[3]='S'; hdr[4]=0; hdr[5]=header_type; wr64le(hdr+6, granule); wr32le(hdr+14, serial); wr32le(hdr+18, seq); wr32le(hdr+22, 0); hdr[26]=(u8)seg_count; buf_append(&page, hdr, 27); buf_append(&page, segs, seg_count); buf_append(&page, body, body_len); crc = ogg_crc(page.p, page.len); wr32le(page.p + 22, crc); buf_append(out, page.p, page.len); buf_free(&page); } /* ---------- ogg packet split writer ---------- */ static void ogg_write_packet_split(Buf* out, const u8* pkt, u32 pkt_len, u32 serial, u32* io_seq, u64 granule_last, u8 header_type_first, u8 header_type_last) { u32 pos=0, seq=*io_seq, max_body=255UL*255UL; while (pos < pkt_len){ u32 remaining = pkt_len - pos; if (remaining <= max_body){ u8 segs[255]; u32 seg_count=0; u32 n=remaining; u8 ht; while (n >= 255){ segs[seg_count++] = 255; n -= 255; } segs[seg_count++] = (u8)n; ht = (pos==0) ? (u8)(header_type_first | header_type_last) : (u8)(0x01 | header_type_last); ogg_build_page_raw(out, segs, seg_count, pkt + pos, remaining, serial, seq, granule_last, ht); seq++; break; } else { u8 segs_full[255]; u32 i; u8 ht; for (i=0;i<255;i++) segs_full[i]=255; ht = (pos==0) ? header_type_first : 0x01; ogg_build_page_raw(out, segs_full, 255, pkt + pos, max_body, serial, seq, 0, ht); seq++; pos += max_body; } } *io_seq = seq; } /* ---------- frame split ---------- */ typedef struct Span { u8* p; u32 len; } Span; static Span* mpeg4_split_vop_frames(const u8* es, u32 es_len, u32* out_count){ u32* idxs; u32 cap=1024, count=0, i; Span* frames; u32 header_len; u8* header; idxs = (u32*)malloc((size_t)cap * sizeof(u32)); if (!idxs) die("Out of memory"); i=0; while (i+4 <= es_len){ u32 j; int found=0; for (j=i; j+4<=es_len; j++){ if (es[j]==0x00 && es[j+1]==0x00 && es[j+2]==0x01 && es[j+3]==0xB6){ found=1; break; } } if (!found) break; if (count >= cap){ u32* np; cap = cap + cap/2 + 256; np = (u32*)realloc(idxs, (size_t)cap * sizeof(u32)); if (!np) die("Out of memory"); idxs=np; } idxs[count++] = j; i = j + 4; } if (count==0){ free(idxs); die("No MPEG-4 VOP start codes (00 00 01 B6) found"); } header_len = idxs[0]; header = 0; if (header_len){ header = (u8*)malloc((size_t)header_len); if (!header) die("Out of memory"); memcpy(header, es, (size_t)header_len); } frames = (Span*)calloc((size_t)count, sizeof(Span)); if (!frames) die("Out of memory"); for (i=0;i<count;i++){ u32 start = idxs[i]; u32 end = (i+1<count) ? idxs[i+1] : es_len; u32 flen = end - start; if (i==0 && header_len){ u32 total = header_len + flen; frames[i].p = (u8*)malloc((size_t)total); if (!frames[i].p) die("Out of memory"); memcpy(frames[i].p, header, (size_t)header_len); memcpy(frames[i].p + header_len, es + start, (size_t)flen); frames[i].len = total; } else { frames[i].p = (u8*)malloc((size_t)flen); if (!frames[i].p) die("Out of memory"); memcpy(frames[i].p, es + start, (size_t)flen); frames[i].len = flen; } } if (header) free(header); free(idxs); *out_count = count; return frames; } static void free_frames(Span* frames, u32 count){ u32 i; if (!frames) return; for (i=0;i<count;i++) if (frames[i].p) free(frames[i].p); free(frames); } /* ---------- dv extract ---------- */ typedef struct FrameMeta { u8 key; } FrameMeta; static void dv_extract(const u8* dv, u32 dv_len, u32* out_w, u32* out_h, u32* out_frames, u32* out_channels, u8** out_video_es, u32* out_video_es_len, u8** out_audio_ogg, u32* out_audio_ogg_len, FrameMeta** out_meta) { u32 magic,w,h,frames2,channels,asize1st; u32 table_off, table_len, preroll_off, data_off; Buf video, audio; FrameMeta* meta; u32 i, pos; u64 sum_total = 0; if (dv_len < 0x4C) die("Input too small for dvf_head"); magic = rd32le(dv + 0); if (magic != MAGIC_DV_LE) die("Bad DV!! signature"); w = rd32le(dv + 16); h = rd32le(dv + 20); frames2 = rd32le(dv + 28); channels = rd32le(dv + 32); asize1st = rd32le(dv + 36); table_off = 0x4C; table_len = frames2 * 0x14UL; preroll_off = table_off + table_len; if (dv_len < preroll_off) die("Truncated: table"); /* Decide data_off: - If no audio (channels==0): no preroll block, data starts immediately after table. - If audio exists: expect fixed 0xC000 preroll block. Only first asize1st bytes are meaningful; rest is padding. If asize1st is invalid (0xFFFFFFFF) or >0xC000: clamp/warn. */ if (channels == 0) { data_off = preroll_off; asize1st = 0; } else { if (dv_len < preroll_off + PREROLL_BLOCK_SIZE) die("Truncated: preroll block"); if (asize1st == 0xFFFFFFFFUL) { fprintf(stderr, "WARNING: asize1st=0xFFFFFFFF, treating as 0.\n"); asize1st = 0; } else if (asize1st > PREROLL_BLOCK_SIZE) { fprintf(stderr, "WARNING: asize1st=0x%08X > 0xC000, clamping to 0xC000.\n", (unsigned)asize1st); asize1st = PREROLL_BLOCK_SIZE; } data_off = preroll_off + PREROLL_BLOCK_SIZE; } buf_init(&video); buf_init(&audio); /* preroll: only used bytes (audio only) */ if (channels != 0 && asize1st != 0) { buf_append(&audio, dv + preroll_off, asize1st); } /* Precompute sum(total) to know if stream is strictly "inside total" (common) */ for (i=0;i<frames2;i++){ u32 base = table_off + i*0x14UL; u32 fsize = rd32le(dv + base + 0); u32 total = (fsize & 0x7FFFFFFFUL); sum_total += (u64)total; } /* If file size matches data_off + sum_total exactly, forbid "external bytes" jumps. (This is critical for 53-99.dv: it matches exactly; external interpretation would desync and truncate.) */ { u64 expected_end = (u64)data_off + sum_total; int strict_inside_total = (expected_end == (u64)dv_len); pos = data_off; meta = (FrameMeta*)calloc((size_t)frames2, sizeof(FrameMeta)); if (!meta) die("Out of memory"); for (i=0;i<frames2;i++){ u32 base = table_off + i*0x14UL; u32 fsize = rd32le(dv + base + 0); u32 vsize = rd32le(dv + base + 4); u32 dsize = rd32le(dv + base + 12); u32 total = (fsize & 0x7FFFFFFFUL); u32 video_len = 0; u32 blob_len = 0; u32 audio_len = 0; meta[i].key = (u8)((fsize & 0x80000000UL) ? 1 : 0); if (total < vsize) die("Corrupt item: total < vsize"); if ((u64)pos + (u64)total > (u64)dv_len) die("Truncated: packet region"); if (channels == 0) { /* Video-only files (21-30.dv): consume everything as video */ if (total) buf_append(&video, dv + pos, total); pos += total; continue; } /* channels!=0 */ if (total == vsize) { /* tail-audio or audio-only */ if (dsize == 0) { video_len = total; audio_len = 0; } else if (dsize <= total) { video_len = total - dsize; audio_len = dsize; } else { /* key fix for 53-99.dv: when dsize>total treat as AUDIO-ONLY packet */ video_len = 0; audio_len = total; } } else { u32 rem = total - vsize; if (dsize == rem) { /* audio right after video */ video_len = vsize; audio_len = rem; blob_len = 0; } else if (dsize < rem) { /* blob between video and audio */ video_len = vsize; blob_len = dsize; audio_len = rem - blob_len; } else { /* dsize > rem: do NOT jump outside 'total' when strict layout matches file size. */ if (strict_inside_total) { if (dsize <= total) { video_len = total - dsize; audio_len = dsize; blob_len = 0; } else { die("Corrupt item: dsize too large"); } } else { /* Non-strict fallback: prefer tail-audio if possible, else treat as external only if bytes exist. */ if (dsize <= total) { video_len = total - dsize; audio_len = dsize; blob_len = 0; } else { die("Corrupt item: dsize too large"); } } } } /* Append video/audio from within the packet (no external moves in strict mode). */ if (video_len) buf_append(&video, dv + pos, video_len); if (audio_len) { if (blob_len) { u32 ao = vsize + blob_len; if (ao + audio_len > total) die("Corrupt item: audio overruns packet"); buf_append(&audio, dv + pos + ao, audio_len); } else { /* tail-audio or audio-only: take last audio_len bytes */ if (audio_len > total) die("Corrupt item: audio_len > total"); buf_append(&audio, dv + pos + (total - audio_len), audio_len); } } pos += total; } } *out_w=w; *out_h=h; *out_frames=frames2; *out_channels=channels; *out_video_es=video.p; *out_video_es_len=video.len; *out_audio_ogg=audio.p; *out_audio_ogg_len=audio.len; *out_meta=meta; } /* ---------- ogm packet builders ---------- */ static void ogm_build_stream_header_video(u8 out52[52], int w, int h, int fps){ double tu_d; u64 time_unit; memset(out52, 0, 52); memcpy(out52 + 0, "video", 5); memcpy(out52 + 8, "XVID", 4); wr32le(out52 + 12, 52); tu_d = 10000000.0 / (double)fps; time_unit = (u64)(tu_d + 0.5); wr64le(out52 + 16, time_unit); wr64le(out52 + 24, (u64)1); wr32le(out52 + 32, 1); wr32le(out52 + 36, 0); out52[40]=0; out52[41]=0; out52[42]=0; out52[43]=0; wr32le(out52 + 44, (u32)w); wr32le(out52 + 48, (u32)h); } static void ogm_make_video_header_pkt(Buf* pkt, int w, int h, int fps){ u8 sh[52]; buf_init(pkt); ogm_build_stream_header_video(sh, w, h, fps); buf_append_u8(pkt, 0x01); buf_append(pkt, sh, 52); } static void ogm_make_video_comment_pkt(Buf* pkt){ const char* vendor="dvbang"; u32 vlen=(u32)strlen(vendor); u8 tmp[4]; buf_init(pkt); buf_append_u8(pkt, 0x03); wr32le(tmp, vlen); buf_append(pkt, tmp, 4); buf_append(pkt, (const u8*)vendor, vlen); wr32le(tmp, 0); buf_append(pkt, tmp, 4); } static void ogm_make_video_data_pkt(Buf* pkt, const u8* frame, u32 frame_len, u8 is_key){ u8 flags = (u8)(0x80 | (is_key ? 0x08 : 0x00)); buf_init(pkt); buf_append_u8(pkt, flags); buf_append_u8(pkt, 1); buf_append_u8(pkt, 0); buf_append(pkt, frame, frame_len); } /* ---------- mux ---------- */ static void mux_ogv(const char* out_path, const OggPage* audio_pages, u32 audio_page_count, u32 sample_rate, int w, int h, int fps, const Span* frames, const FrameMeta* meta, u32 frame_count, int has_audio) { FILE* fo; Buf out; u32 audio_serial, video_serial, hdr_end; u32 vseq, ai, v_i; double fps_d, half_frame; Buf pkt; buf_init(&out); video_serial = VIDEO_SERIAL_FIXED; if (has_audio) { if (audio_page_count==0) die("No audio pages"); audio_serial = audio_pages[0].serial; if (video_serial == audio_serial) video_serial ^= 0xFFFFFFFFUL; hdr_end = vorbis_header_end_page_index(audio_pages, audio_page_count); /* audio BOS */ buf_append(&out, audio_pages[0].raw, audio_pages[0].raw_len); /* video BOS */ vseq = 0; ogm_make_video_header_pkt(&pkt, w, h, fps); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (u64)0, (u8)0x02, (u8)0x00); buf_free(&pkt); /* video comment */ ogm_make_video_comment_pkt(&pkt); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (u64)0, (u8)0x00, (u8)0x00); buf_free(&pkt); /* copy remaining vorbis header pages */ for (ai=1; ai<=hdr_end && ai<audio_page_count; ai++){ buf_append(&out, audio_pages[ai].raw, audio_pages[ai].raw_len); } fps_d = (double)fps; half_frame = 0.5 / fps_d; v_i = 0; for (ai=hdr_end+1; ai<audio_page_count; ai++){ double at = 0.0; if (sample_rate) at = (double)audio_pages[ai].granule / (double)sample_rate; while (v_i < frame_count){ double vt_next = (double)(v_i + 1) / fps_d; if (vt_next <= at + half_frame){ u8 eos = (u8)((v_i + 1 == frame_count) ? 0x04 : 0x00); u64 gran = (u64)(v_i + 1); u8 is_key = meta ? meta[v_i].key : 1; ogm_make_video_data_pkt(&pkt, frames[v_i].p, frames[v_i].len, is_key); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, gran, (u8)0x00, eos); buf_free(&pkt); v_i++; } else break; } buf_append(&out, audio_pages[ai].raw, audio_pages[ai].raw_len); } while (v_i < frame_count){ u8 eos = (u8)((v_i + 1 == frame_count) ? 0x04 : 0x00); u64 gran = (u64)(v_i + 1); u8 is_key = meta ? meta[v_i].key : 1; ogm_make_video_data_pkt(&pkt, frames[v_i].p, frames[v_i].len, is_key); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, gran, (u8)0x00, eos); buf_free(&pkt); v_i++; } } else { /* Video-only: write only video stream (no audio BOS) */ vseq = 0; ogm_make_video_header_pkt(&pkt, w, h, fps); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (u64)0, (u8)0x02, (u8)0x00); buf_free(&pkt); ogm_make_video_comment_pkt(&pkt); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (u64)0, (u8)0x00, (u8)0x00); buf_free(&pkt); v_i = 0; while (v_i < frame_count){ u8 eos = (u8)((v_i + 1 == frame_count) ? 0x04 : 0x00); u64 gran = (u64)(v_i + 1); ogm_make_video_data_pkt(&pkt, frames[v_i].p, frames[v_i].len, 1); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, gran, (u8)0x00, eos); buf_free(&pkt); v_i++; } } fo = fopen(out_path, "wb"); if (!fo) die("Cannot open output file"); if (out.len && fwrite(out.p, 1, (size_t)out.len, fo) != (size_t)out.len){ fclose(fo); die("Write failed"); } fclose(fo); buf_free(&out); } /* ---------- cli ---------- */ static int streq(const char* a, const char* static void usage(const char* exe){ fprintf(stderr, "Usage:\n %s <input> [output.ogv] [--fps N] [--validate]\n\n" "Options:\n" " --fps N set fps (default 25)\n" " --validate ffmpeg decode test (optional; requires ffmpeg in PATH)\n" " -h,--help help\n", exe); } static void make_default_out(const char* in, char* out, u32 outsz){ const char* dot = strrchr(in, '.'); u32 n; if (!dot){ snprintf(out, outsz, "%s.ogv", in); return; } n = (u32)(dot - in); if (n + 4 + 1 >= outsz) die("Output path too long"); memcpy(out, in, (size_t)n); memcpy(out + n, ".ogv", 5); } int main(int argc, char** argv){ const char* in_path=0; const char* out_arg=0; char out_path[1024]; int validate=0; int fps=25; u8* dv; u32 dv_len; u32 w,h,frames2,channels; u8* video_es; u32 video_es_len; u8* audio_ogg; u32 audio_ogg_len; FrameMeta* meta; OggPage* audio_pages=0; u32 audio_page_count=0; u32 sample_rate=0; Span* frames; u32 frame_count; int i, positional=0; if (argc < 2){ usage(argv[0]); return 2; } for (i=1;i<argc;i++){ const char* a = argv[i]; if (streq(a,"-h") || streq(a,"--help")){ usage(argv[0]); return 0; } if (streq(a,"--validate")){ validate=1; continue; } if (streq(a,"--fps")){ if (i+1>=argc){ fprintf(stderr,"Missing value for --fps\n\n"); usage(argv[0]); return 2; } fps = atoi(argv[++i]); if (fps<=0 || fps>240){ fprintf(stderr,"Invalid fps: %d\n", fps); return 2; } continue; } if (a[0]=='-'){ fprintf(stderr,"Unknown option: %s\n\n", a); usage(argv[0]); return 2; } if (positional==0){ in_path=a; positional++; continue; } if (positional==1){ out_arg=a; positional++; continue; } fprintf(stderr,"Too many arguments\n\n"); usage(argv[0]); return 2; } if (!in_path){ usage(argv[0]); return 2; } if (out_arg){ strncpy(out_path, out_arg, sizeof(out_path)-1); out_path[sizeof(out_path)-1]=0; } else { make_default_out(in_path, out_path, (u32)sizeof(out_path)); } crc_table_init(); dv = read_file_all(in_path, &dv_len); if (!dv) die2("Cannot read input file: ", in_path); w=h=frames2=channels=0; video_es=0; video_es_len=0; audio_ogg=0; audio_ogg_len=0; meta=0; dv_extract(dv, dv_len, &w, &h, &frames2, &channels, &video_es, &video_es_len, &audio_ogg, &audio_ogg_len, &meta); frames = mpeg4_split_vop_frames(video_es, video_es_len, &frame_count); if (channels != 0) { audio_pages = ogg_parse_pages(audio_ogg, audio_ogg_len, &audio_page_count); if (!audio_pages || audio_page_count==0) die("Audio parse failed: no Ogg pages found"); sample_rate = vorbis_detect_sample_rate(audio_pages, audio_page_count); if (!sample_rate){ fprintf(stderr,"WARNING: could not detect Vorbis sample rate; assuming 44100.\n"); sample_rate = 44100; } } if (frames2 && frame_count != frames2){ fprintf(stderr,"WARNING: frame count mismatch (table=%u, VOP=%u). Continuing.\n", (unsigned)frames2, (unsigned)frame_count); } mux_ogv(out_path, audio_pages, audio_page_count, sample_rate, (int)w, (int)h, fps, frames, meta, frame_count, (channels != 0)); printf("ogv: %s\n", out_path); printf("video: MPEG-4 Part 2 (%ux%u, frames=%u, fps=%d)\n", (unsigned)w, (unsigned)h, (unsigned)frame_count, fps); if (channels != 0) { printf("audio: Vorbis (sr=%u, pages=%u, channels=%u)\n", (unsigned)sample_rate, (unsigned)audio_page_count, (unsigned)channels); } else { printf("audio: none\n"); } if (validate){ char cmd[1400]; int rc; snprintf(cmd, sizeof(cmd), "ffmpeg -hide_banner -loglevel error -i \"%s\" -f null -", out_path); rc = system(cmd); if (rc != 0) fprintf(stderr,"WARNING: ffmpeg validation failed (ffmpeg missing or decode errors).\n"); } if (dv) free(dv); if (video_es) free(video_es); if (audio_ogg) free(audio_ogg); if (audio_pages) ogg_free_pages(audio_pages, audio_page_count); if (frames) free_frames(frames, frame_count); if (meta) free(meta); return 0; } Спасибо сказали:
|
| Lumis |
Mar 5 2026, 09:29
Сообщение
#7
|
|
Member ![]() ![]() Группа: Authorized Сообщений: 11 Регистрация: 2-March 26 Пользователь №: 18,038 Спасибо сказали: 11 раз(а) |
Кажется, удалось побороть проблемы с артефактами на файле 53-99.dv. Теперь у меня по крайней мере все файлы перегоняет чисто, ошибок ffmpeg не вижу. Проверите на mplayer?
CODE /* dvbang2ogv.c * ANSI C / C89, Visual Studio 2008 friendly * License: MIT, Copyright 2026 Lumis * v1.3 fixes 53-99.dv * * Converts proprietary "DV!!" container into .ogv (OGM). * - Video: MPEG-4 Part 2 ES (byte-exact). * - Audio: Ogg/Vorbis pages copied (byte-exact). * - Muxing in-code; large OGM packets are split across Ogg pages (no "Too many segments"). * * Container format: * * dvf_head (0x4C bytes, little-endian u32 fields): * magic = "DV!!" (0x21215644 LE) * width = hdr[4] * height = hdr[5] * frames2 = hdr[7] (count of items/frames) * channels = hdr[8] (0 no audio, 2 stereo) * asize1st = hdr[9] (used bytes in preroll area; rest is zero padding) * * then: item table: frames2 * 0x14 bytes: * fsize: total packet bytes, with keyframe flag in bit31 * vsize: video bytes inside packet * zero1 * dsize: audio bytes inside packet (in many files dsize == total - vsize) * zero2 * * then: preroll audio region of 0xC000 bytes may exist (often padded with zeros); * only first asize1st bytes are meaningful audio bytes. * * then: frames2 packets of (fsize & 0x7fffffff) bytes. * packet layout: * video[vsize] + blob[blob_size] + audio[audio_size] * * where: * tail = total - vsize * audio_size= min(dsize, tail) <-- critical fix (53-99.dv needs this) * blob_size = tail - audio_size * * OGV/OGM mux: * - Copy Vorbis BOS page unchanged (byte-exact) * - Create OGM video BOS page (stream_header, subtype "XVID") * - Create OGM video comment page * - Copy remaining Vorbis header pages unchanged (until 3 Vorbis packets end) * - Interleave: * at = audio_page.granule / sample_rate * emit video frames while ((v_i+1)/fps) <= at + (0.5/fps) * then copy audio page * Finally flush remaining video frames. * * Determinism: * - video serial hardcoded to 0xC0DEC0DE (xor if equals audio serial). * * Build (small-ish EXE): * cl /O1 /Os /GL /GS- /Gy /GR- /EHsc- /MD dvbang2ogv.c /link /LTCG /OPT:REF /OPT:ICF /SUBSYSTEM:CONSOLE,5.01 */ #include <stdio.h> #include <stdlib.h> #include <string.h> /* ----- Fixed-size types for VC2008 (no stdint.h) ----- */ typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; typedef unsigned __int64 u64; #ifdef _MSC_VER #ifndef snprintf #define snprintf _snprintf #endif #endif #define MAGIC_DV_LE 0x21215644UL /* "DV!!" little-endian */ #define PREROLL_BLOCK_SIZE 0xC000UL #define VIDEO_SERIAL_FIXED 0xC0DEC0DEUL /* ------------------ Error handling ------------------ */ static void die(const char* msg) { fprintf(stderr, "ERROR: %s\n", msg); exit(2); } static void die2(const char* msg, const char* more) { fprintf(stderr, "ERROR: %s%s\n", msg, more ? more : ""); exit(2); } /* ------------------ Little-endian helpers ------------------ */ static u32 rd32le(const u8* p) { return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24); } static u64 rd64le(const u8* p) { u64 v = 0; v |= (u64)p[0]; v |= (u64)p[1] << 8; v |= (u64)p[2] << 16; v |= (u64)p[3] << 24; v |= (u64)p[4] << 32; v |= (u64)p[5] << 40; v |= (u64)p[6] << 48; v |= (u64)p[7] << 56; return v; } static void wr32le(u8* p, u32 v) { p[0] = (u8)(v & 0xFF); p[1] = (u8)((v >> 8) & 0xFF); p[2] = (u8)((v >> 16) & 0xFF); p[3] = (u8)((v >> 24) & 0xFF); } static void wr64le(u8* p, u64 v) { p[0] = (u8)(v & 0xFF); p[1] = (u8)((v >> 8) & 0xFF); p[2] = (u8)((v >> 16) & 0xFF); p[3] = (u8)((v >> 24) & 0xFF); p[4] = (u8)((v >> 32) & 0xFF); p[5] = (u8)((v >> 40) & 0xFF); p[6] = (u8)((v >> 48) & 0xFF); p[7] = (u8)((v >> 56) & 0xFF); } /* ------------------ Dynamic buffer ------------------ */ typedef struct Buf { u8* p; u32 len; u32 cap; } Buf; /* Initializes a growable byte buffer. */ static void buf_init(Buf* b) { b->p = 0; b->len = 0; b->cap = 0; } /* Ensures capacity >= need, grows with slack to reduce realloc churn. */ static void buf_reserve(Buf* b, u32 need) { u32 ncap; u8* np; if (need <= b->cap) return; ncap = b->cap ? (b->cap + (b->cap >> 1) + 64) : 256; if (ncap < need) ncap = need; np = (u8*)realloc(b->p, (size_t)ncap); if (!np) die("Out of memory"); b->p = np; b->cap = ncap; } /* Appends n bytes to buffer. */ static void buf_append(Buf* b, const u8* data, u32 n) { if (!n) return; buf_reserve(b, b->len + n); memcpy(b->p + b->len, data, (size_t)n); b->len += n; } /* Appends a single byte. */ static void buf_append_u8(Buf* b, u8 v) { buf_reserve(b, b->len + 1); b->p[b->len++] = v; } /* Frees buffer memory. */ static void buf_free(Buf* b) { if (b->p) free(b->p); b->p = 0; b->len = 0; b->cap = 0; } /* ------------------ File I/O ------------------ */ static u8* read_file_all(const char* path, u32* out_len) { FILE* f; long sz; u8* data; f = fopen(path, "rb"); if (!f) return 0; fseek(f, 0, SEEK_END); sz = ftell(f); if (sz < 0) { fclose(f); return 0; } fseek(f, 0, SEEK_SET); data = (u8*)malloc((size_t)sz); if (!data) { fclose(f); return 0; } if (sz > 0 && fread(data, 1, (size_t)sz, f) != (size_t)sz) { free(data); fclose(f); return 0; } fclose(f); *out_len = (u32)sz; return data; } /* ------------------ Ogg CRC ------------------ */ static u32 CRC_TABLE[256]; /* Builds CRC lookup table for Ogg pages. */ static void crc_table_init(void) { u32 poly, i, j, r; poly = 0x04C11DB7UL; for (i = 0; i < 256; i++) { r = i << 24; for (j = 0; j < 8; j++) { if (r & 0x80000000UL) r = (r << 1) ^ poly; else r <<= 1; } CRC_TABLE[i] = r; } } /* Computes Ogg page CRC over entire page bytes (CRC field must be set to 0 before computing). */ static u32 ogg_crc(const u8* page, u32 len) { u32 crc, i; crc = 0; for (i = 0; i < len; i++) { u8 b = page[i]; crc = (crc << 8) ^ CRC_TABLE[((crc >> 24) & 0xFF) ^ b]; } return crc; } /* ------------------ Ogg page builder (raw) ------------------ */ /* Writes a complete page from already-laced segments + body, patches CRC, appends to 'out'. */ static void ogg_build_page_raw(Buf* out, const u8* segs, u32 seg_count, const u8* body, u32 body_len, u32 serial, u32 seq, u64 granule, u8 header_type) { u8 hdr[27]; Buf page; u32 crc; memset(hdr, 0, sizeof(hdr)); hdr[0] = 'O'; hdr[1] = 'g'; hdr[2] = 'g'; hdr[3] = 'S'; hdr[4] = 0; hdr[5] = header_type; wr64le(hdr + 6, granule); wr32le(hdr + 14, serial); wr32le(hdr + 18, seq); wr32le(hdr + 22, 0); hdr[26] = (u8)seg_count; buf_init(&page); buf_append(&page, hdr, 27); buf_append(&page, segs, seg_count); buf_append(&page, body, body_len); crc = ogg_crc(page.p, page.len); wr32le(page.p + 22, crc); buf_append(out, page.p, page.len); buf_free(&page); } /* ------------------ Packet -> multiple pages (fix 255 segment limit) ------------------ */ /* Ogg allows max 255 lacing values per page. For huge packets we split across multiple pages: * - intermediate pages: 255 segments of 255 bytes (max body 65025), header_type CONTINUATION as needed. * - final page: remaining body with proper granule, EOS on last if requested. * * granule_last is only set on the final page (as typical for Ogg). */ static void ogg_write_packet_split(Buf* out, const u8* pkt, u32 pkt_len, u32 serial, u32* io_seq, u64 granule_last, u8 header_first, /* e.g. BOS for first packet page */ u8 header_last) /* e.g. EOS for last packet page */ { const u32 MAX_BODY = 255UL * 255UL; /* 65025 */ u32 pos = 0; u32 seq = *io_seq; while (pos < pkt_len) { u32 rem = pkt_len - pos; if (rem <= MAX_BODY) { /* Final page for this packet */ u8 segs[255]; u32 seg_count = 0; u32 n = rem; u8 ht; while (n >= 255) { segs[seg_count++] = 255; n -= 255; } segs[seg_count++] = (u8)n; /* If it's not the first chunk -> must set continuation flag. */ if (pos == 0) ht = (u8)(header_first | header_last); else ht = (u8)(0x01 | header_last); ogg_build_page_raw(out, segs, seg_count, pkt + pos, rem, serial, seq, granule_last, ht); seq++; break; } else { /* Full chunk page (not final) */ u8 segs_full[255]; u32 i; u8 ht; for (i = 0; i < 255; i++) segs_full[i] = 255; ht = (pos == 0) ? header_first : 0x01; /* continuation for non-first */ ogg_build_page_raw(out, segs_full, 255, pkt + pos, MAX_BODY, serial, seq, 0, ht); seq++; pos += MAX_BODY; } } *io_seq = seq; } /* ------------------ Ogg page parser (minimal) ------------------ */ typedef struct OggPage { u8 header_type; u64 granule; u32 serial; u32 seq; u8* segtable; u32 segtable_len; u8* body; u32 body_len; u8* bytes; u32 bytes_len; } OggPage; /* Parses Ogg pages from contiguous Ogg stream buffer; keeps raw bytes per page. */ static OggPage* ogg_parse_pages(const u8* data, u32 len, u32* out_count) { OggPage* pages; u32 cap, count; u32 i; cap = 64; count = 0; pages = (OggPage*)calloc((size_t)cap, sizeof(OggPage)); if (!pages) die("Out of memory"); i = 0; while (1) { u32 j, k, body_len, body_off, seg_off, end; int found; u8 nseg; found = 0; for (j = i; j + 4 <= len; j++) { if (data[j]=='O' && data[j+1]=='g' && data[j+2]=='g' && data[j+3]=='S') { found = 1; break; } } if (!found) break; if (j + 27 > len) break; nseg = data[j + 26]; seg_off = j + 27; body_off = seg_off + (u32)nseg; if (body_off > len) break; body_len = 0; for (k = 0; k < (u32)nseg; k++) body_len += data[seg_off + k]; end = body_off + body_len; if (end > len) break; if (count >= cap) { OggPage* np; cap = cap + cap/2 + 32; np = (OggPage*)realloc(pages, (size_t)cap * sizeof(OggPage)); if (!np) die("Out of memory"); pages = np; memset(pages + count, 0, (size_t)(cap - count) * sizeof(OggPage)); } pages[count].header_type = data[j + 5]; pages[count].granule = rd64le(data + j + 6); pages[count].serial = rd32le(data + j + 14); pages[count].seq = rd32le(data + j + 18); pages[count].segtable_len = (u32)nseg; pages[count].segtable = (u8*)malloc((size_t)nseg); pages[count].body = (u8*)malloc((size_t)body_len); pages[count].bytes_len = end - j; pages[count].bytes = (u8*)malloc((size_t)pages[count].bytes_len); if (!pages[count].segtable || !pages[count].body || !pages[count].bytes) die("Out of memory"); memcpy(pages[count].segtable, data + seg_off, (size_t)nseg); memcpy(pages[count].body, data + body_off, (size_t)body_len); memcpy(pages[count].bytes, data + j, (size_t)pages[count].bytes_len); pages[count].body_len = body_len; count++; i = end; } *out_count = count; return pages; } /* Frees parsed pages. */ static void ogg_free_pages(OggPage* pages, u32 count) { u32 i; if (!pages) return; for (i = 0; i < count; i++) { if (pages[i].segtable) free(pages[i].segtable); if (pages[i].body) free(pages[i].body); if (pages[i].bytes) free(pages[i].bytes); } free(pages); } /* ------------------ Vorbis helpers ------------------ */ /* Detects sample rate from Vorbis identification packet (type 0x01 'vorbis'). */ static u32 vorbis_detect_sample_rate(const OggPage* pages, u32 count) { Buf cur; u32 i, sidx; buf_init(&cur); for (i = 0; i < count; i++) { u32 off = 0; for (sidx = 0; sidx < pages[i].segtable_len; sidx++) { u8 s = pages[i].segtable[sidx]; buf_append(&cur, pages[i].body + off, (u32)s); off += (u32)s; if (s < 255) { if (cur.len >= 16 && cur.p[0] == 0x01 && cur.p[1]=='v' && cur.p[2]=='o' && cur.p[3]=='r' && cur.p[4]=='b' && cur.p[5]=='i' && cur.p[6]=='s') { u32 sr = rd32le(cur.p + 12); buf_free(&cur); return sr; } /* next packet */ cur.len = 0; } } } buf_free(&cur); return 0; } /* Returns index of page where Vorbis headers (3 packets) end. */ static u32 vorbis_header_end_page_index(const OggPage* pages, u32 count) { u32 pkt_count, i, sidx; pkt_count = 0; for (i = 0; i < count; i++) { for (sidx = 0; sidx < pages[i].segtable_len; sidx++) { if (pages[i].segtable[sidx] < 255) { pkt_count++; if (pkt_count >= 3) return i; } } } return count ? (count - 1) : 0; } /* ------------------ DV extraction ------------------ */ typedef struct FrameMeta { u8 key; } FrameMeta; /* Extracts video ES + audio Ogg from DV!! file. Handles both with/without preroll block. */ static void dv_extract(const u8* dv, u32 dv_len, u32* out_w, u32* out_h, u32* out_frames, u32* out_channels, u8** out_video_es, u32* out_video_es_len, u8** out_audio_ogg, u32* out_audio_ogg_len, FrameMeta** out_meta) { u32 w, h, frames2, channels, asize1st; u32 table_off, table_len, preroll_off; u32 i; u64 sum_total; u32 data_off_with_preroll, data_off_no_preroll, data_off; int has_preroll; Buf video, audio; FrameMeta* meta; u32 pos; if (dv_len < 0x4C) die("Input too small for DV!! header"); if (rd32le(dv + 0) != MAGIC_DV_LE) die("Bad DV!! signature"); w = rd32le(dv + 16); h = rd32le(dv + 20); frames2 = rd32le(dv + 28); channels = rd32le(dv + 32); asize1st = rd32le(dv + 36); table_off = 0x4C; table_len = frames2 * 0x14UL; preroll_off = table_off + table_len; if (dv_len < preroll_off) die("Truncated: frame table"); /* Compute exact byte sum of all packets */ sum_total = 0; for (i = 0; i < frames2; i++) { u32 base = table_off + i * 0x14UL; u32 fsize = rd32le(dv + base + 0); sum_total += (u64)(fsize & 0x7FFFFFFFUL); } data_off_no_preroll = preroll_off; data_off_with_preroll = preroll_off + PREROLL_BLOCK_SIZE; /* Prefer strict size match. */ if ((u64)data_off_with_preroll + sum_total == (u64)dv_len) has_preroll = 1; else if ((u64)data_off_no_preroll + sum_total == (u64)dv_len) has_preroll = 0; else { /* Fallback: if file doesn't match exactly, pick the variant that fits and warn. */ if (data_off_with_preroll < dv_len && (u64)data_off_with_preroll + sum_total < (u64)dv_len) { fprintf(stderr, "WARNING: size mismatch; assuming preroll block present.\n"); has_preroll = 1; } else if ((u64)data_off_no_preroll + sum_total < (u64)dv_len) { fprintf(stderr, "WARNING: size mismatch; assuming no preroll block.\n"); has_preroll = 0; } else { die("Cannot determine payload offset (size mismatch too large)"); } } data_off = has_preroll ? data_off_with_preroll : data_off_no_preroll; /* Sanitize preroll length */ if (channels == 0) asize1st = 0; else { if (asize1st == 0xFFFFFFFFUL) asize1st = 0; if (asize1st > PREROLL_BLOCK_SIZE) { fprintf(stderr, "WARNING: asize1st > 0xC000; clamping.\n"); asize1st = PREROLL_BLOCK_SIZE; } } buf_init(&video); buf_init(&audio); /* Copy meaningful preroll audio bytes */ if (channels != 0 && has_preroll && asize1st) { if (preroll_off + asize1st > dv_len) die("Truncated: preroll audio"); buf_append(&audio, dv + preroll_off, asize1st); } meta = (FrameMeta*)calloc((size_t)frames2, sizeof(FrameMeta)); if (!meta) die("Out of memory"); pos = data_off; for (i = 0; i < frames2; i++) { u32 base = table_off + i * 0x14UL; u32 fsize = rd32le(dv + base + 0); u32 vsize = rd32le(dv + base + 4); u32 dsize = rd32le(dv + base + 12); u32 total = (fsize & 0x7FFFFFFFUL); const u8* pkt; meta[i].key = (u8)((fsize & 0x80000000UL) ? 1 : 0); if ((u64)pos + (u64)total > (u64)dv_len) die("Truncated: frame data"); pkt = dv + pos; if (channels == 0) { /* video-only */ if (total) buf_append(&video, pkt, total); pos += total; continue; } /* audio+video */ if (total < vsize) die("Corrupt item: total < vsize"); /* Always append video bytes */ if (vsize) buf_append(&video, pkt, vsize); /* Tail may contain blob + audio */ if (total > vsize) { u32 tail = total - vsize; u32 audio_size; u32 blob_size; /* Critical fix: * dsize is the audio byte count in many files (53-99.dv: dsize == tail). * If dsize exceeds tail -> treat as unknown, take all tail as audio. */ audio_size = (dsize <= tail) ? dsize : tail; blob_size = tail - audio_size; if (audio_size) { buf_append(&audio, pkt + vsize + blob_size, audio_size); } } pos += total; } *out_w = w; *out_h = h; *out_frames = frames2; *out_channels = channels; *out_video_es = video.p; *out_video_es_len = video.len; *out_audio_ogg = audio.p; *out_audio_ogg_len = audio.len; *out_meta = meta; } /* ------------------ MPEG-4 Part 2 frame splitting ------------------ */ typedef struct Span { u8* p; u32 len; } Span; /* Splits ES by VOP start codes (00 00 01 B6). * Prepends global header (everything before first VOP) to the first frame. */ static Span* mpeg4_split_vop_frames(const u8* es, u32 es_len, u32* out_count) { u32* idxs; u32 cap, count, i; Span* frames; u32 header_len; u8* header; cap = 1024; count = 0; idxs = (u32*)malloc((size_t)cap * sizeof(u32)); if (!idxs) die("Out of memory"); i = 0; while (i + 4 <= es_len) { u32 j; int found; found = 0; for (j = i; j + 4 <= es_len; j++) { if (es[j]==0x00 && es[j+1]==0x00 && es[j+2]==0x01 && es[j+3]==0xB6) { found = 1; break; } } if (!found) break; if (count >= cap) { u32* np; cap = cap + cap/2 + 256; np = (u32*)realloc(idxs, (size_t)cap * sizeof(u32)); if (!np) die("Out of memory"); idxs = np; } idxs[count++] = j; i = j + 4; } if (count == 0) { free(idxs); die("No MPEG-4 VOP start codes found"); } header_len = idxs[0]; header = 0; if (header_len) { header = (u8*)malloc((size_t)header_len); if (!header) die("Out of memory"); memcpy(header, es, (size_t)header_len); } frames = (Span*)calloc((size_t)count, sizeof(Span)); if (!frames) die("Out of memory"); for (i = 0; i < count; i++) { u32 start, end, flen; start = idxs[i]; end = (i + 1 < count) ? idxs[i+1] : es_len; flen = end - start; if (i == 0 && header_len) { u32 total = header_len + flen; frames[i].p = (u8*)malloc((size_t)total); if (!frames[i].p) die("Out of memory"); memcpy(frames[i].p, header, (size_t)header_len); memcpy(frames[i].p + header_len, es + start, (size_t)flen); frames[i].len = total; } else { frames[i].p = (u8*)malloc((size_t)flen); if (!frames[i].p) die("Out of memory"); memcpy(frames[i].p, es + start, (size_t)flen); frames[i].len = flen; } } if (header) free(header); free(idxs); *out_count = count; return frames; } static void free_frames(Span* frames, u32 count) { u32 i; if (!frames) return; for (i = 0; i < count; i++) if (frames[i].p) free(frames[i].p); free(frames); } /* ------------------ OGM packet builders ------------------ */ /* Builds 52-byte OGM stream_header for video (subtype "XVID"). */ static void ogm_build_stream_header_video(u8 out52[52], int w, int h, int fps) { double tu_d; u64 time_unit; memset(out52, 0, 52); memcpy(out52 + 0, "video", 5); memcpy(out52 + 8, "XVID", 4); wr32le(out52 + 12, 52); tu_d = 10000000.0 / (double)fps; time_unit = (u64)(tu_d + 0.5); wr64le(out52 + 16, time_unit); wr64le(out52 + 24, (u64)1); wr32le(out52 + 32, 1); wr32le(out52 + 36, 0); wr32le(out52 + 44, (u32)w); wr32le(out52 + 48, (u32)h); } /* Creates OGM video BOS packet: 0x01 + stream_header. */ static void ogm_make_video_header_pkt(Buf* pkt, int w, int h, int fps) { u8 sh[52]; buf_init(pkt); ogm_build_stream_header_video(sh, w, h, fps); buf_append_u8(pkt, 0x01); buf_append(pkt, sh, 52); } /* Creates OGM comment packet: 0x03 + vendor + no user comments (Vorbis-style comment payload). */ static void ogm_make_video_comment_pkt(Buf* pkt) { const char* vendor = "dvbang"; u32 vlen; u8 tmp[4]; vlen = (u32)strlen(vendor); buf_init(pkt); buf_append_u8(pkt, 0x03); wr32le(tmp, vlen); buf_append(pkt, tmp, 4); buf_append(pkt, (const u8*)vendor, vlen); wr32le(tmp, 0); buf_append(pkt, tmp, 4); } /* Creates one OGM video data packet: * flags: lenbytes=2 (0x80), keyframe (0x08 optionally) * duration samples: 1 (LE16) * payload: MPEG4 frame bytes */ static void ogm_make_video_data_pkt(Buf* pkt, const u8* frame, u32 frame_len, u8 is_key) { u8 flags; buf_init(pkt); flags = (u8)(0x80 | (is_key ? 0x08 : 0x00)); buf_append_u8(pkt, flags); buf_append_u8(pkt, 1); buf_append_u8(pkt, 0); buf_append(pkt, frame, frame_len); } /* ------------------ Mux (writes .ogv) ------------------ */ static void mux_ogv(const char* out_path, const OggPage* audio_pages, u32 audio_page_count, u32 sample_rate, int w, int h, int fps, const Span* frames, const FrameMeta* meta, u32 frame_count, int has_audio) { FILE* fo; Buf out; u32 video_serial, audio_serial; u32 hdr_end; u32 vseq, v_i; double fps_d, half_frame; Buf pkt; u32 ai; buf_init(&out); video_serial = VIDEO_SERIAL_FIXED; if (has_audio) { if (audio_page_count == 0) die("No audio pages"); audio_serial = audio_pages[0].serial; if (video_serial == audio_serial) video_serial ^= 0xFFFFFFFFUL; hdr_end = vorbis_header_end_page_index(audio_pages, audio_page_count); /* Copy Vorbis BOS page unchanged */ buf_append(&out, audio_pages[0].bytes, audio_pages[0].bytes_len); /* Video BOS page */ vseq = 0; ogm_make_video_header_pkt(&pkt, w, h, fps); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (u64)0, (u8)0x02, (u8)0x00); buf_free(&pkt); /* Video comment page */ ogm_make_video_comment_pkt(&pkt); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (u64)0, (u8)0x00, (u8)0x00); buf_free(&pkt); /* Copy remaining Vorbis header pages unchanged */ for (ai = 1; ai <= hdr_end && ai < audio_page_count; ai++) { buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len); } fps_d = (double)fps; half_frame = 0.5 / fps_d; v_i = 0; /* Interleave by audio page timestamps */ for (ai = hdr_end + 1; ai < audio_page_count; ai++) { double at; at = 0.0; if (sample_rate) at = (double)audio_pages[ai].granule / (double)sample_rate; while (v_i < frame_count) { double vt_next; u8 eos_flag; u64 gran; vt_next = (double)(v_i + 1) / fps_d; if (vt_next <= at + half_frame) { eos_flag = (u8)((v_i + 1 == frame_count) ? 0x04 : 0x00); gran = (u64)(v_i + 1); ogm_make_video_data_pkt(&pkt, frames[v_i].p, frames[v_i].len, meta ? meta[v_i].key : 1); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, gran, (u8)0x00, eos_flag); buf_free(&pkt); v_i++; } else { break; } } /* Copy current audio page unchanged */ buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len); } } else { /* No audio: write only video stream (still valid Ogg/OGM). */ vseq = 0; ogm_make_video_header_pkt(&pkt, w, h, fps); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (u64)0, (u8)0x02, (u8)0x00); buf_free(&pkt); ogm_make_video_comment_pkt(&pkt); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (u64)0, (u8)0x00, (u8)0x00); buf_free(&pkt); v_i = 0; } /* Flush remaining video frames (if any) */ while (v_i < frame_count) { u8 eos_flag; u64 gran; eos_flag = (u8)((v_i + 1 == frame_count) ? 0x04 : 0x00); gran = (u64)(v_i + 1); ogm_make_video_data_pkt(&pkt, frames[v_i].p, frames[v_i].len, meta ? meta[v_i].key : 1); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, gran, (u8)0x00, eos_flag); buf_free(&pkt); v_i++; } /* Write to disk */ fo = fopen(out_path, "wb"); if (!fo) die("Cannot open output file"); if (out.len && fwrite(out.p, 1, (size_t)out.len, fo) != (size_t)out.len) { fclose(fo); die("Write failed"); } fclose(fo); buf_free(&out); } /* ------------------ CLI helpers ------------------ */ static int streq(const char* a, const char* b) { return strcmp(a, b) == 0; } static void usage(const char* exe) { fprintf(stderr, "Usage:\n" " %s <input.dv> [output.ogv] [--fps N] [--validate]\n\n" "Options:\n" " --fps N set fps (default 25)\n" " --validate run ffmpeg decode test (optional; ffmpeg must be in PATH)\n" " -h,--help show help\n", exe ); } static void make_default_out(const char* in, char* out, u32 outsz) { const char* dot; u32 n; dot = strrchr(in, '.'); if (!dot) { snprintf(out, outsz, "%s.ogv", in); return; } n = (u32)(dot - in); if (n + 5 >= outsz) die("Output path too long"); memcpy(out, in, (size_t)n); memcpy(out + n, ".ogv", 5); } /* ------------------ main ------------------ */ int main(int argc, char** argv) { const char* in_path; const char* out_arg; char out_path[1024]; int validate; int fps; u8* dv; u32 dv_len; u32 w, h, frames2, channels; u8* video_es; u32 video_es_len; u8* audio_ogg; u32 audio_ogg_len; FrameMeta* meta; OggPage* audio_pages; u32 audio_page_count; u32 sample_rate; Span* frames; u32 frame_count; int i; int positional; /* defaults */ in_path = 0; out_arg = 0; validate = 0; fps = 25; if (argc < 2) { usage(argv[0]); return 2; } positional = 0; for (i = 1; i < argc; i++) { const char* a = argv[i]; if (streq(a, "-h") || streq(a, "--help")) { usage(argv[0]); return 0; } if (streq(a, "--validate")) { validate = 1; continue; } if (streq(a, "--fps")) { if (i + 1 >= argc) { fprintf(stderr, "Missing value for --fps\n\n"); usage(argv[0]); return 2; } fps = atoi(argv[++i]); if (fps <= 0 || fps > 240) { fprintf(stderr, "Invalid fps: %d\n", fps); return 2; } continue; } if (a[0] == '-') { fprintf(stderr, "Unknown option: %s\n\n", a); usage(argv[0]); return 2; } if (positional == 0) { in_path = a; positional++; continue; } if (positional == 1) { out_arg = a; positional++; continue; } fprintf(stderr, "Too many arguments.\n\n"); usage(argv[0]); return 2; } if (!in_path) { usage(argv[0]); return 2; } if (out_arg) { strncpy(out_path, out_arg, sizeof(out_path) - 1); out_path[sizeof(out_path) - 1] = 0; } else { make_default_out(in_path, out_path, (u32)sizeof(out_path)); } crc_table_init(); dv = read_file_all(in_path, &dv_len); if (!dv) die2("Cannot read input file: ", in_path); /* Extract */ w = h = frames2 = channels = 0; video_es = 0; video_es_len = 0; audio_ogg = 0; audio_ogg_len = 0; meta = 0; dv_extract(dv, dv_len, &w, &h, &frames2, &channels, &video_es, &video_es_len, &audio_ogg, &audio_ogg_len, &meta); /* Split frames */ frames = mpeg4_split_vop_frames(video_es, video_es_len, &frame_count); /* Parse audio if present */ audio_pages = 0; audio_page_count = 0; sample_rate = 0; if (channels != 0) { audio_pages = ogg_parse_pages(audio_ogg, audio_ogg_len, &audio_page_count); if (!audio_pages || audio_page_count == 0) die("Audio parse failed: no Ogg pages found"); sample_rate = vorbis_detect_sample_rate(audio_pages, audio_page_count); if (sample_rate == 0) { fprintf(stderr, "WARNING: could not detect Vorbis sample rate; assuming 44100.\n"); sample_rate = 44100; } } /* Mux */ mux_ogv(out_path, audio_pages, audio_page_count, sample_rate, (int)w, (int)h, fps, frames, meta, frame_count, (channels != 0)); printf("ogv: %s\n", out_path); printf("video: MPEG-4 Part 2 (%ux%u, frames=%u, fps=%d)\n", w, h, frame_count, fps); if (channels != 0) printf("audio: Vorbis (sr=%u, pages=%u)\n", sample_rate, audio_page_count); else printf("audio: none\n"); /* Optional validation */ if (validate) { char cmd[1400]; int rc; snprintf(cmd, sizeof(cmd), "ffmpeg -hide_banner -loglevel error -i \"%s\" -f null -", out_path); rc = system(cmd); if (rc != 0) { fprintf(stderr, "WARNING: ffmpeg validation failed (ffmpeg missing or decode errors).\n"); } } /* cleanup */ if (dv) free(dv); if (video_es) free(video_es); if (audio_ogg) free(audio_ogg); if (audio_pages) ogg_free_pages(audio_pages, audio_page_count); if (frames) free_frames(frames, frame_count); if (meta) free(meta); return 0; } Спасибо сказали:
|
| -=CHE@TER=- |
Mar 6 2026, 00:30
Сообщение
#8
|
|
Walter Sullivan ![]() ![]() ![]() Группа: Root Admin Сообщений: 1,428 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 327 раз(а) |
Lumis, спасибо большое за исправленные версии!
Вношу некоторые коррективы по результатам более подробного исследования кода этих игр. 1. Правильно данные читаются вот так: - видео - звук (если есть) - выравнивание (если есть) Проверил по дизассемблерному коду обеих игр - "АЖ" ("Ангел желаний") и "ГЭГ2". Поэтому я и написал выше, что правильно читать кадры так: QUOTE data = fsize & 0x7FFFFFFF // сбрасываем старший бит Можно попросить поправить этот момент? Спасибо!data = data - vsize // размер видео всегда правильный, отнимаем от общего размера если asize больше data, тогда asize = data data = data - asize // отнимаем размер аудио - должен остаться только размер выравнивания (если есть) видео = прочитать vsize байт аудио = прочитать asize байт пропустить data байт И прошу прощения за "dsize" - я поменял структуру в своём сообщении выше и вернул обратно имя "asize" - это размер звука (не всегда верный, но размер, asize = a(udio)size). 2. Код "dvbang2ogv.c" из последнего сообщения работает правильно. Проверял только на следующих файлах: "11-15_1.dv" - "АЖ", не помню в чём там дело было, но мой конвертер на нём падал раньше, так что оставил для тестов; "21-30.dv" - "АЖ", файл без звука; "kleo9.avi" - "ГЭГ2", большой файл, удобно для проверки; "timetravel.avi" - "ГЭГ2", некоторые кадры не умещаются в размер пакета OggS 255*255 байт, так что использую для проверки, что кадры по пакетам разбиваются верно. 3. Я попытался начисто переписать AI'шный код (большое спасибо за предоставленный код! подсмотрел пару толковых идей), чтобы наверняка было, и, вроде бы, всё правильно, но файлы, которые у меня получаются, проигрываются с ошибками. Я тестировал на "kleo9.avi" из "ГЭГ2", он достаточно большой и на 18 секунде у меня в MPlayer линуксовом картинка разваливается. Виндовый MPC-HC (Media Player Classic Home Cinema) проигрывает без проблем. Код ниже прилагаю, что не так - я понять не могу. Сравнил по блокам с тем что "dvbang2ogv.c" делает - почему-то "dvbang2ogv.c" добавляет начало ключевого кадра в хвост предыдущему, в то время как я целиком как кадры были сохранены в оригинале записываю их в файл (что, по идее, должно было бы быть правильнее), но из-за этого лезут ошибки при проигрывании. Ничего не понимаю. У меня в коде сначала склеивается в один буфер целиком видео, а во второй целиком звук (если есть) и уже только потом всё пишется в файл с чередованием. Если звук и видео сохранить как отдельные файлы, то они линуксовым MPlayer проигрываются без ошибок: mplayer test.divx -audiofile test.ogg Но в .OGM проигрывается почему-то с ошибками. Код моего "dv2ogvm.c", если нужно: CODE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> /*#define DEBUG 1*/ /* https://web.archive.org/web/20051220042025/...:80/packfmt.htm */ #define OGGSHEAD 0x5367674F #define OGG_VID_ID 0 #define OGG_AUD_ID 1 #define DV_VID_FPS 25 #define DV_VID_BPP 12 #define DV_VID_FCC 0x44495658 #define DV_AUD_1ST 0xC000 #define PACKET_TYPE_DATA 0x00 /* for data blocks? */ #define PACKET_TYPE_HEADER 0x01 #define PACKET_TYPE_COMMENT 0x03 #define PACKET_TYPE_BITS 0x07 #define PACKET_LEN_BITS01 0xC0 #define PACKET_LEN_BITS2 0x02 #define PACKET_IS_SYNCPOINT 0x08 /* for key-frames? */ #pragma pack(push, 1) typedef struct { uint32_t magic; /* "DV!!" */ uint32_t zero1; uint32_t frames1; /* unused */ uint32_t unknown; /* ??? */ uint32_t width; /* video width */ uint32_t height; /* video height */ uint32_t zero2; uint32_t frames2; uint32_t channels; /* 0 - no sound; 2 - stereo */ uint32_t asize1st; /* audio data size preroll */ uint32_t zero3[9]; } dvf_head; /* sizeof(dvf_head) == 0x4C */ typedef struct { uint32_t fsize; /* same as vsize+asize but with highest bit set (0x80000000) if key-frame */ uint32_t vsize; /* video frame size (always valid) */ uint32_t zero1; uint32_t asize; /* audio data size (maybe incorrect!) */ uint32_t zero2; } dvf_item; typedef struct { /* uint8_t PacketType; *//* 0x01 --- PACKET_TYPE_HEADER */ char StreamType[8]; /* "video\0\0\0" */ uint32_t FourCC; /* "XVID" */ uint32_t StructureSize; /* 0x38 */ uint64_t TimeUnit; /* in 1/10000000 of seconds --- 400000 (ie 10000000 / 25) */ uint64_t SamplesPerUnit; /* fps = 10000000 * SamplesPerUnit / TimeUnit --- 1 */ uint32_t DefaultLength; /* 0x01 */ uint32_t DecodeBufferSize; /* w * h * 3 */ uint16_t BitsPerPixel; /* 24 */ uint16_t padding1; uint32_t PixelWidth; uint32_t PixelHeight; uint32_t padding2; } ogg_hvid; /* sizeof(ogg_ivid) == 0x38 + 1 */ typedef struct { /* uint8_t PacketType;*/ /* 0x01 */ char VorbisCodec[6]; /* "vorbis" */ uint32_t VorbisVersion; /* 0x00 */ uint8_t ChannelsNumber; /* 2 - stereo */ uint32_t SamplesPerSecond; /* 44100 */ uint32_t MinBitrate; uint32_t MidBitrate; uint32_t MaxBitrate; uint8_t TwoNibble; uint8_t Flag; } ogg_haud; typedef struct { uint32_t magic; /* "OggS" */ uint8_t build; /* Stream structure version (0x00) */ uint8_t flags; /* Packet flag: 1:page continued; 2:first page; 4:last page; else - reserved */ uint64_t sdend; /* The end pcm sample position (64bit integer) */ uint32_t srnum; /* Stream serial number */ uint32_t pgnum; /* Page number */ uint32_t crc32; /* Check sum */ uint8_t sgnum; /* Number of segments(s) (1-byte) followed */ } ogg_head; typedef struct { ogg_head hogg; uint8_t hlen; uint8_t tpak; ogg_hvid hvid; } ogg_vhdr; typedef struct { ogg_head hogg; uint8_t hlen; uint8_t tpak; ogg_haud haud; } ogg_ahdr; #pragma pack(pop) static uint32_t crc32table[256]; /* making the 32-bit CRC table */ static void crc32maketable(void) { uint32_t i, j, r; for (i = 0; i < 256; i++) { r = (i << 24); for (j = 0; j < 8; j++) { r = (r << 1) ^ ((r & 0x80000000) ? 0x04C11DB7 : 0); } crc32table[i] = r; } } /* Updating existed 32-bit CRC with new calaculated */ static uint32_t crc32update(uint8_t *p, uint32_t sz, uint32_t crcval) { uint32_t i; for (i = 0; i < sz; i++) { crcval = (crcval << 8) ^ crc32table[(crcval >> 24) ^ *p]; p++; } return(crcval); } /* CRC32 buffer */ static uint32_t crc32buffer(void *p, uint32_t sz) { return(crc32update((uint8_t *) p, sz, 0)); } static char *basename(char *s) { char *r; if (s) { for (r = s; *r; r++) { if ((*r == ':') || (*r == '/') || (*r == '\\')) { s = &r[1]; } } } return(s); } static void newext(char *name, char *ext) { char *p; if (name && ext) { for (; (*name == '.'); name++); for (p = NULL; *name; name++) { if (*name == '.') { p = name; } } strcpy(p ? p : name, ext); } } static void readdata(FILE *fl, void *data, uint32_t size) { if (data && size) { memset(data, 0, size); if (fl) { fread(data, size, 1, fl); } } } #ifdef DEBUG static void dumpdata(char *name, void *data, uint32_t size) { FILE *f; if (name) { f = fopen(name, "wb"); if (f) { if (data && size) { fwrite(data, size, 1, f); } fclose(f); } } } #endif static uint16_t ogg_size(uint8_t *p) { uint16_t l, i; ogg_head *h; l = 0; h = (ogg_head *) p; if ((h) && (h->magic == OGGSHEAD)) { l = sizeof(h[0]) + h->sgnum; p += sizeof(h[0]); for (i = 0; i < h->sgnum; i++) { l += p[i]; } } return(l); } static void write_video_frame(FILE *fl, ogg_head *h, uint8_t *p, uint32_t l, uint32_t flag) { uint32_t sz, k; uint8_t b[3 + 2]; if ((fl) && (h) && (p) && (l) && (h->magic == OGGSHEAD)) { flag &= (4 | 2); /* key frame and last packet only */ flag |= 1; /* first packet */ b[3] = 1; b[4] = 0; while (l > 0) { /* max packet size */ sz = (255 * 255) - ((flag & 1)*3); sz = (sz <= l) ? sz : l; l -= sz; h->sgnum = ((sz + ((flag & 1)*3)) + 254) / 255; /* last packet */ h->flags = ((!l) && (flag & 2)) ? 0x04 : 0x00; /* packet continued from last one? */ h->flags |= ((flag & 1) ^ 1); h->pgnum++; h->crc32 = 0; h->crc32 = crc32buffer(h, sizeof(h[0])); b[0] = 0xFF; b[1] = (sz + ((flag & 1)*3)) % 255; b[1] = b[1] ? b[1] : b[0]; /* key frame */ if (flag & 1) { b[2] = 0x80 | PACKET_TYPE_DATA | ((flag & 4) ? PACKET_IS_SYNCPOINT : 0); } for (k = 0; k < h->sgnum; k++) { h->crc32 = crc32update(&b[((k + 1) == h->sgnum) ? 1 : 0], 1, h->crc32); } /* only 1st packet in frame: update crc32 */ if (flag & 1) { h->crc32 = crc32update(&b[2], 1+2, h->crc32); } h->crc32 = crc32update(p, sz, h->crc32); /* write header */ fwrite(h, sizeof(h[0]), 1, fl); /* write segments */ for (k = 0; k < h->sgnum; k++) { fwrite(&b[((k + 1) == h->sgnum) ? 1 : 0], 1, 1, fl); } /* only 1st packet in frame: write type */ if (flag & 1) { fwrite(&b[2], 1+2, 1, fl); } /* write data */ fwrite(p, sz, 1, fl); p += sz; /* only 1st packet in frame: update frame number */ if (flag & 1) { h->sdend++; } /* drop first packet flag */ flag = (flag | 1) ^ 1; } } } static uint32_t write_audio_frame(FILE *fl, uint8_t *paud, uint32_t l, uint32_t laud) { ogg_head *h; uint32_t m; if ((fl) && (paud) && (l < laud)) { m = ogg_size(&paud[l]); /* valid frame */ if (m) { h = (ogg_head *) &paud[l]; h->srnum = OGG_AUD_ID; /* new stream serial number */ /* update checksum */ h->crc32 = 0; h->crc32 = crc32buffer(&paud[l], m); /* write to disk */ fwrite(&paud[l], m, 1, fl); } /* advance to the next frame */ l += m; } return(l); } int main(int argc, char *argv[]) { uint32_t i, j, l, k, hz, lvid, laud, vend, vnow; uint8_t *pvid, *paud; ogg_head *hogg; ogg_vhdr svid; dvf_head head; dvf_item *list; char s[260]; FILE *fl; printf(".DV to .OGM video converter v1.0\n© Lumis 2026\nLicense: MIT\n\n"); if (argc != 2) { printf("Usage: dv2ogvm <filename.dv>\n\n"); return(1); } fl = fopen(argv[1], "rb"); if (!fl) { printf("Error: can't open input file.\n\n"); return(2); } fseek(fl, 0, SEEK_END); i = ftell(fl); fseek(fl, 0, SEEK_SET); readdata(fl, &head, sizeof(head)); if ( (i <= sizeof(head)) || (head.magic != 0x21215644) || (!head.frames2) || (!head.width) || (!head.height) ) { fclose(fl); printf("Error: invalid input file format.\n\n"); return(3); } i = head.frames2 * sizeof(list[0]); list = (dvf_item *) malloc(i); if (!list) { fclose(fl); printf("Error: not enough memory for contents table.\n\n"); return(4); } readdata(fl, list, i); /* part 1 - load and parsing input file */ pvid = NULL; paud = NULL; /* fix audio related header */ head.asize1st = head.channels ? head.asize1st : 0; head.asize1st = (head.asize1st <= DV_AUD_1ST) ? head.asize1st : DV_AUD_1ST; for (j = 0; j < 2; j++) { lvid = 0; laud = head.asize1st; /* since head.asize1st already fixed before loop */ for (i = 0; i < head.frames2; i++) { #ifdef DEBUG if (!j) { printf("%08X %08X %08X %08X %08X\n", list[i].fsize, list[i].vsize, list[i].zero1, list[i].asize, list[i].zero2); } #endif if (!j) { /* proper block size */ list[i].zero1 = list[i].fsize & 0x7FFFFFFF; /* video size seems to be always valid */ list[i].zero1 -= list[i].vsize; /* trim audio packet size */ list[i].asize = (list[i].asize <= list[i].zero1) ? list[i].asize : list[i].zero1; /* use remaining bytes as padding */ list[i].zero1 -= list[i].asize; } /* second pass - merge data blocks together */ if (j) { /* starts with some audio data */ if (!i) { /* if audio track there */ if (laud) { readdata(fl, &paud[0], laud); /* first audio packed always padded to 0xC000 */ if (laud < DV_AUD_1ST) { fseek(fl, ftell(fl) + DV_AUD_1ST - laud, SEEK_SET); } } } readdata(fl, &pvid[lvid], list[i].vsize); readdata(fl, &paud[laud], list[i].asize); /* padding */ fseek(fl, ftell(fl) + list[i].zero1, SEEK_SET); #ifdef DEBUG if (list[1].zero1) { printf("Frame #%u padding %u\n", i, list[i].zero1); } #endif } #ifdef DEBUG if ((!j) && (list[i].fsize & 0x80000000)) { printf("KEY_FRAME: %08X\n", lvid); } #endif lvid += list[i].vsize; laud += list[i].asize; } /* first pass - allocate memory buffers */ if (!j) { /* no video?.. */ if (!lvid) { break; } pvid = (uint8_t *) malloc(lvid); paud = (uint8_t *) malloc(laud + 1); /* make sure audio always here */ if ((!pvid) || (!paud)) { break; } } } fclose(fl); if ((!pvid) || (!paud)) { if (pvid) { free(pvid); } if (paud) { free(paud); } printf("Error: not enough memory for input data.\n\n"); return(5); } #ifdef DEBUG dumpdata("test.divx", pvid, lvid); dumpdata("test.ogg", paud, laud); #endif /* part 2 - remuxing into .OGM format */ strcpy(s, basename(argv[1])); printf("%s -> ", s); newext(s, ".ogm"); printf("%s\n", s); crc32maketable(); /* parse .OGG file to find audio header */ memset(&svid, 0, sizeof(svid)); fl = fopen(s, "wb"); if (fl) { /* in .OGG format all streams header packets MUST come first before any data packets! */ /* video header */ memcpy(svid.hvid.StreamType, "video", 5); svid.hvid.FourCC = DV_VID_FCC; svid.hvid.StructureSize = sizeof(svid.hvid); svid.hvid.TimeUnit = 10000000 / DV_VID_FPS; svid.hvid.SamplesPerUnit = 1; svid.hvid.DefaultLength = 1; /* XVID yuv420 has 12 bits per pixel */ l = ((head.width * DV_VID_BPP) + 4) / 8; /* bytes per line */ l += (4 - (l & 3)) & 3; /* 32 bit align just in case */ l *= head.height; svid.hvid.DecodeBufferSize = l; svid.hvid.BitsPerPixel = DV_VID_BPP; svid.hvid.PixelWidth = head.width; svid.hvid.PixelHeight = head.height; svid.hogg.magic = OGGSHEAD; svid.hogg.flags = 0x02; /* first packet for this stream */ svid.hogg.srnum = OGG_VID_ID; /* video serial number */ svid.hogg.sgnum = 1; svid.hlen = sizeof(svid.hvid) + 1; svid.tpak = PACKET_TYPE_HEADER; svid.hogg.crc32 = crc32buffer(&svid, sizeof(svid)); fwrite(&svid, sizeof(svid), 1, fl); /* audio header */ l = write_audio_frame(fl, paud, 0, laud); /* sound frequency */ hz = 0; if ((l) && (paud) && (laud)) { hz = *((uint32_t *) &paud[sizeof(hogg[0]) + 1 + 12]); } if ((hz < 8000) || (hz > 48000)) { hz = 44100; } /* video comment */ svid.hogg.flags = 0; svid.hlen = 1 + 4 + 4; svid.tpak = PACKET_TYPE_COMMENT; svid.hogg.pgnum++; svid.hogg.crc32 = 0; svid.hogg.crc32 = crc32buffer(&svid.hogg, sizeof(svid.hogg) + 1 + 1); /* vendor string length */ k = 0; svid.hogg.crc32 = crc32update((uint8_t *) &k, 4, svid.hogg.crc32); /* user comment string length */ svid.hogg.crc32 = crc32update((uint8_t *) &k, 4, svid.hogg.crc32); fwrite(&svid.hogg, sizeof(svid.hogg) + 1 + 1, 1, fl); fwrite(&k, 4, 1, fl); fwrite(&k, 4, 1, fl); /* interleave audio/video blocks */ i = 0; j = 0; vnow = 0; vend = 0; while (l < laud) { /* audio frame */ hogg = (ogg_head *) &paud[l]; k = write_audio_frame(fl, paud, l, laud); if (l == k) { printf("Warning: failed to advance OggS stream: %u of %u bytes.\n\n", l, laud); break; } l = k; /* video frames */ vend = (vend < hogg->sdend) ? hogg->sdend : vend; while ((vnow < vend) && (i < head.frames2)) { write_video_frame( fl, &svid.hogg, &pvid[j], list[i].vsize, ((list[i].fsize & 0x80000000) ? 4 : 0) /* key frame */ | (( (i + 1) == head.frames2) ? 2 : 0) /* last packet */ ); j += list[i].vsize; i++; vnow += hz / DV_VID_FPS; } } /* write remaining video frames */ while (i < head.frames2) { write_video_frame( fl, &svid.hogg, &pvid[j], list[i].vsize, ((list[i].fsize & 0x80000000) ? 4 : 0) /* key frame */ | (( (i + 1) == head.frames2) ? 2 : 0) /* last packet */ ); j += list[i].vsize; i++; } fclose(fl); } else { printf("Error: can't create output file.\n\n"); } /* cleanup */ free(paud); free(pvid); free(list); printf("\ndone\n\n"); return(fl ? 0 : 6); } |
| Lumis |
Mar 6 2026, 22:33
Сообщение
#9
|
|
Member ![]() ![]() Группа: Authorized Сообщений: 11 Регистрация: 2-March 26 Пользователь №: 18,038 Спасибо сказали: 11 раз(а) |
Но в .OGM проигрывается почему-то с ошибками. Мне кажется что у вас в муксинге проблема: OGM вы пишете неправильно по порядку заголовков потоков. Вот это место у вас ломает совместимость с нормальными Ogg/Vorbis демультиплексерами: CODE /* in .OGG format all streams header packets MUST come first before any data packets! */ /* video header */ ... /* audio header */ l = write_audio_frame(fl, paud, 0, laud); /* video comment */ ... /* interleave audio/video blocks */ while (l < laud) { ... } Вы записываете:
А нужно:
Нужно сначала выписать все header pages обоих потоков. Для вашего подхода это значит:
Сейчас у вас пишется только первая audio header page, а остальные заголовочные audio pages попадают уже в общий цикл. Вот это и неправильно. Также видмо лучше не использовать write_audio_frame() как "и header, и data" без разделения: у вас сейчас одна и та же функция пишет и header page, и обычные data pages. Это само по себе допустимо, но нужно отдельно сначала определить, где заканчиваются Vorbis headers. Иначе вы не знаете, сколько аудио-страниц надо записать перед началом data. почему-то "dvbang2ogv.c" добавляет начало ключевого кадра в хвост предыдущему, в то время как я целиком как кадры были сохранены в оригинале записываю их в файл (что, по идее, должно было бы быть правильнее), но из-за этого лезут ошибки при проигрывании Это часть стандарта в MPEG-4 Part 2: перед первым VOP могут идти VOL/VOS/global headers, некоторые декодеры ожидают, что они будут доступны вместе с первым кадром. Это не обязательно, но желательно, как я понимаю. Ещё одна слабая точка в вашем mux - вот эта логика: CODE vend = (vend < hogg->sdend) ? hogg->sdend : vend; while ((vnow < vend) && (i < head.frames2)) { ... vnow += hz / DV_VID_FPS; } Она работает иногда случайно, но правильнее сравнивать не накопленные целые sample counts, а время: CODE audio_time = granule / sample_rate video_time_next = (frame_index + 1) / fps и писать видео пока: CODE video_time_next <= audio_time + 0.5/fps Спасибо сказали:
|
| Lumis |
Mar 6 2026, 22:46
Сообщение
#10
|
|
Member ![]() ![]() Группа: Authorized Сообщений: 11 Регистрация: 2-March 26 Пользователь №: 18,038 Спасибо сказали: 11 раз(а) |
Lumis, спасибо большое за исправленные версии! Вношу некоторые коррективы по результатам более подробного исследования кода этих игр. 1. Правильно данные читаются вот так: - видео - звук (если есть) - выравнивание (если есть) Проверил по дизассемблерному коду обеих игр - "АЖ" ("Ангел желаний") и "ГЭГ2". Поэтому я и написал выше, что правильно читать кадры так: Можно попросить поправить этот момент? Спасибо! И прошу прощения за "dsize" - я поменял структуру в своём сообщении выше и вернул обратно имя "asize" - это размер звука (не всегда верный, но размер, asize = a(udio)size). Спасибо за анализ! Исправил, так логика ещё чуть проще: CODE /* dvbang2ogv.c * ANSI C / C89, Visual Studio 2008 friendly * License: MIT, Copyright 2026 Lumis * v1.4 fixes frame order: video = vsize; audio = min(dsize, total - vsize); padding = (total - vsize) - audio * * Converts proprietary "DV!!" .dv container into .ogv (OGM). * Video: MPEG-4 Part 2 (VOP start code 00 00 01 B6) kept byte-exact. * Audio: Ogg/Vorbis stream bytes kept byte-exact (no re-encode). * * Container format (as reverse engineered): * * dvf_head (0x4C bytes, little-endian u32 fields): * magic = "DV!!" (0x21215644 LE) * width = hdr[4] * height = hdr[5] * frames2 = hdr[7] (count of items/frames) * channels = hdr[8] (0 no audio, 2 stereo) * asize1st = hdr[9] (used bytes in preroll area; rest is zero padding) * * then: item table: frames2 * 0x14 bytes: * fsize: total packet bytes, with keyframe flag in bit31 * vsize: video bytes inside packet * zero1 * dsize: audio bytes inside packet (in many files dsize == total - vsize) * zero2 * * then: preroll audio region of 0xC000 bytes may exist (often padded with zeros); * only first asize1st bytes are meaningful audio bytes. * * then: frames2 packets of (fsize & 0x7fffffff) bytes. * packet layout: * video[vsize] + blob[blob_size] + audio[audio_size] * * where: * tail = total - vsize * audio_size= min(dsize, tail) <-- critical fix (53-99.dv needs this) * blob_size = tail - audio_size * * OGV/OGM mux: * - Copy Vorbis BOS page unchanged (byte-exact) * - Create OGM video BOS page (stream_header, subtype "XVID") * - Create OGM video comment page * - Copy remaining Vorbis header pages unchanged (until 3 Vorbis packets end) * - Interleave: * at = audio_page.granule / sample_rate * emit video frames while ((v_i+1)/fps) <= at + (0.5/fps) * then copy audio page * Finally flush remaining video frames. * * Determinism: * - video serial hardcoded to 0xC0DEC0DE (xor if equals audio serial). * * Build (small-ish EXE): * cl /O1 /Os /GL /GS- /Gy /GR- /EHsc- /MD dvbang2ogv.c /link /LTCG /OPT:REF /OPT:ICF /SUBSYSTEM:CONSOLE,5.01 */ #include <stdio.h> #include <stdlib.h> #include <string.h> /* ----- Fixed-size types for VC2008 (no stdint.h) ----- */ typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; typedef unsigned __int64 u64; #ifdef _MSC_VER #ifndef snprintf #define snprintf _snprintf #endif #endif #define MAGIC_DV_LE 0x21215644UL /* "DV!!" little-endian */ #define PREROLL_BLOCK_SIZE 0xC000UL #define VIDEO_SERIAL_FIXED 0xC0DEC0DEUL /* ------------------ Error handling ------------------ */ static void die(const char* msg) { fprintf(stderr, "ERROR: %s\n", msg); exit(2); } static void die2(const char* msg, const char* more) { fprintf(stderr, "ERROR: %s%s\n", msg, more ? more : ""); exit(2); } /* ------------------ Little-endian helpers ------------------ */ static u32 rd32le(const u8* p) { return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24); } static u64 rd64le(const u8* p) { u64 v = 0; v |= (u64)p[0]; v |= (u64)p[1] << 8; v |= (u64)p[2] << 16; v |= (u64)p[3] << 24; v |= (u64)p[4] << 32; v |= (u64)p[5] << 40; v |= (u64)p[6] << 48; v |= (u64)p[7] << 56; return v; } static void wr32le(u8* p, u32 v) { p[0] = (u8)(v & 0xFF); p[1] = (u8)((v >> 8) & 0xFF); p[2] = (u8)((v >> 16) & 0xFF); p[3] = (u8)((v >> 24) & 0xFF); } static void wr64le(u8* p, u64 v) { p[0] = (u8)(v & 0xFF); p[1] = (u8)((v >> 8) & 0xFF); p[2] = (u8)((v >> 16) & 0xFF); p[3] = (u8)((v >> 24) & 0xFF); p[4] = (u8)((v >> 32) & 0xFF); p[5] = (u8)((v >> 40) & 0xFF); p[6] = (u8)((v >> 48) & 0xFF); p[7] = (u8)((v >> 56) & 0xFF); } /* ------------------ Dynamic buffer ------------------ */ typedef struct Buf { u8* p; u32 len; u32 cap; } Buf; /* Initializes a growable byte buffer. */ static void buf_init(Buf* B) { b->p = 0; b->len = 0; b->cap = 0; } /* Ensures capacity >= need, grows with slack to reduce realloc churn. */ static void buf_reserve(Buf* b, u32 need) { u32 ncap; u8* np; if (need <= b->cap) return; ncap = b->cap ? (b->cap + (b->cap >> 1) + 64) : 256; if (ncap < need) ncap = need; np = (u8*)realloc(b->p, (size_t)ncap); if (!np) die("Out of memory"); b->p = np; b->cap = ncap; } /* Appends n bytes to buffer. */ static void buf_append(Buf* b, const u8* data, u32 n) { if (!n) return; buf_reserve(b, b->len + n); memcpy(b->p + b->len, data, (size_t)n); b->len += n; } /* Appends a single byte. */ static void buf_append_u8(Buf* b, u8 v) { buf_reserve(b, b->len + 1); b->p[b->len++] = v; } /* Frees buffer memory. */ static void buf_free(Buf* B) { if (b->p) free(b->p); b->p = 0; b->len = 0; b->cap = 0; } /* ------------------ File I/O ------------------ */ static u8* read_file_all(const char* path, u32* out_len) { FILE* f; long sz; u8* data; f = fopen(path, "rb"); if (!f) return 0; fseek(f, 0, SEEK_END); sz = ftell(f); if (sz < 0) { fclose(f); return 0; } fseek(f, 0, SEEK_SET); data = (u8*)malloc((size_t)sz); if (!data) { fclose(f); return 0; } if (sz > 0 && fread(data, 1, (size_t)sz, f) != (size_t)sz) { free(data); fclose(f); return 0; } fclose(f); *out_len = (u32)sz; return data; } /* ------------------ Ogg CRC ------------------ */ static u32 CRC_TABLE[256]; /* Builds CRC lookup table for Ogg pages. */ static void crc_table_init(void) { u32 poly, i, j, r; poly = 0x04C11DB7UL; for (i = 0; i < 256; i++) { r = i << 24; for (j = 0; j < 8; j++) { if (r & 0x80000000UL) r = (r << 1) ^ poly; else r <<= 1; } CRC_TABLE[i] = r; } } /* Computes Ogg page CRC over entire page bytes (CRC field must be set to 0 before computing). */ static u32 ogg_crc(const u8* page, u32 len) { u32 crc, i; crc = 0; for (i = 0; i < len; i++) { u8 b = page[i]; crc = (crc << 8) ^ CRC_TABLE[((crc >> 24) & 0xFF) ^ b]; } return crc; } /* ------------------ Ogg page builder (raw) ------------------ */ /* Writes a complete page from already-laced segments + body, patches CRC, appends to 'out'. */ static void ogg_build_page_raw(Buf* out, const u8* segs, u32 seg_count, const u8* body, u32 body_len, u32 serial, u32 seq, u64 granule, u8 header_type) { u8 hdr[27]; Buf page; u32 crc; memset(hdr, 0, sizeof(hdr)); hdr[0] = 'O'; hdr[1] = 'g'; hdr[2] = 'g'; hdr[3] = 'S'; hdr[4] = 0; hdr[5] = header_type; wr64le(hdr + 6, granule); wr32le(hdr + 14, serial); wr32le(hdr + 18, seq); wr32le(hdr + 22, 0); hdr[26] = (u8)seg_count; buf_init(&page); buf_append(&page, hdr, 27); buf_append(&page, segs, seg_count); buf_append(&page, body, body_len); crc = ogg_crc(page.p, page.len); wr32le(page.p + 22, crc); buf_append(out, page.p, page.len); buf_free(&page); } /* ------------------ Packet -> multiple pages (fix 255 segment limit) ------------------ */ /* Ogg allows max 255 lacing values per page. For huge packets we split across multiple pages: * - intermediate pages: 255 segments of 255 bytes (max body 65025), header_type CONTINUATION as needed. * - final page: remaining body with proper granule, EOS on last if requested. * * granule_last is only set on the final page (as typical for Ogg). */ static void ogg_write_packet_split(Buf* out, const u8* pkt, u32 pkt_len, u32 serial, u32* io_seq, u64 granule_last, u8 header_first, /* e.g. BOS for first packet page */ u8 header_last) /* e.g. EOS for last packet page */ { const u32 MAX_BODY = 255UL * 255UL; /* 65025 */ u32 pos = 0; u32 seq = *io_seq; while (pos < pkt_len) { u32 rem = pkt_len - pos; if (rem <= MAX_BODY) { /* Final page for this packet */ u8 segs[255]; u32 seg_count = 0; u32 n = rem; u8 ht; while (n >= 255) { segs[seg_count++] = 255; n -= 255; } segs[seg_count++] = (u8)n; /* If it's not the first chunk -> must set continuation flag. */ if (pos == 0) ht = (u8)(header_first | header_last); else ht = (u8)(0x01 | header_last); ogg_build_page_raw(out, segs, seg_count, pkt + pos, rem, serial, seq, granule_last, ht); seq++; break; } else { /* Full chunk page (not final) */ u8 segs_full[255]; u32 i; u8 ht; for (i = 0; i < 255; i++) segs_full[i] = 255; ht = (pos == 0) ? header_first : 0x01; /* continuation for non-first */ ogg_build_page_raw(out, segs_full, 255, pkt + pos, MAX_BODY, serial, seq, 0, ht); seq++; pos += MAX_BODY; } } *io_seq = seq; } /* ------------------ Ogg page parser (minimal) ------------------ */ typedef struct OggPage { u8 header_type; u64 granule; u32 serial; u32 seq; u8* segtable; u32 segtable_len; u8* body; u32 body_len; u8* bytes; u32 bytes_len; } OggPage; /* Parses Ogg pages from contiguous Ogg stream buffer; keeps raw bytes per page. */ static OggPage* ogg_parse_pages(const u8* data, u32 len, u32* out_count) { OggPage* pages; u32 cap, count; u32 i; cap = 64; count = 0; pages = (OggPage*)calloc((size_t)cap, sizeof(OggPage)); if (!pages) die("Out of memory"); i = 0; while (1) { u32 j, k, body_len, body_off, seg_off, end; int found; u8 nseg; found = 0; for (j = i; j + 4 <= len; j++) { if (data[j]=='O' && data[j+1]=='g' && data[j+2]=='g' && data[j+3]=='S') { found = 1; break; } } if (!found) break; if (j + 27 > len) break; nseg = data[j + 26]; seg_off = j + 27; body_off = seg_off + (u32)nseg; if (body_off > len) break; body_len = 0; for (k = 0; k < (u32)nseg; k++) body_len += data[seg_off + k]; end = body_off + body_len; if (end > len) break; if (count >= cap) { OggPage* np; cap = cap + cap/2 + 32; np = (OggPage*)realloc(pages, (size_t)cap * sizeof(OggPage)); if (!np) die("Out of memory"); pages = np; memset(pages + count, 0, (size_t)(cap - count) * sizeof(OggPage)); } pages[count].header_type = data[j + 5]; pages[count].granule = rd64le(data + j + 6); pages[count].serial = rd32le(data + j + 14); pages[count].seq = rd32le(data + j + 18); pages[count].segtable_len = (u32)nseg; pages[count].segtable = (u8*)malloc((size_t)nseg); pages[count].body = (u8*)malloc((size_t)body_len); pages[count].bytes_len = end - j; pages[count].bytes = (u8*)malloc((size_t)pages[count].bytes_len); if (!pages[count].segtable || !pages[count].body || !pages[count].bytes) die("Out of memory"); memcpy(pages[count].segtable, data + seg_off, (size_t)nseg); memcpy(pages[count].body, data + body_off, (size_t)body_len); memcpy(pages[count].bytes, data + j, (size_t)pages[count].bytes_len); pages[count].body_len = body_len; count++; i = end; } *out_count = count; return pages; } /* Frees parsed pages. */ static void ogg_free_pages(OggPage* pages, u32 count) { u32 i; if (!pages) return; for (i = 0; i < count; i++) { if (pages[i].segtable) free(pages[i].segtable); if (pages[i].body) free(pages[i].body); if (pages[i].bytes) free(pages[i].bytes); } free(pages); } /* ------------------ Vorbis helpers ------------------ */ /* Detects sample rate from Vorbis identification packet (type 0x01 'vorbis'). */ static u32 vorbis_detect_sample_rate(const OggPage* pages, u32 count) { Buf cur; u32 i, sidx; buf_init(&cur); for (i = 0; i < count; i++) { u32 off = 0; for (sidx = 0; sidx < pages[i].segtable_len; sidx++) { u8 s = pages[i].segtable[sidx]; buf_append(&cur, pages[i].body + off, (u32)s); off += (u32)s; if (s < 255) { if (cur.len >= 16 && cur.p[0] == 0x01 && cur.p[1]=='v' && cur.p[2]=='o' && cur.p[3]=='r' && cur.p[4]=='b' && cur.p[5]=='i' && cur.p[6]=='s') { u32 sr = rd32le(cur.p + 12); buf_free(&cur); return sr; } /* next packet */ cur.len = 0; } } } buf_free(&cur); return 0; } /* Returns index of page where Vorbis headers (3 packets) end. */ static u32 vorbis_header_end_page_index(const OggPage* pages, u32 count) { u32 pkt_count, i, sidx; pkt_count = 0; for (i = 0; i < count; i++) { for (sidx = 0; sidx < pages[i].segtable_len; sidx++) { if (pages[i].segtable[sidx] < 255) { pkt_count++; if (pkt_count >= 3) return i; } } } return count ? (count - 1) : 0; } /* ------------------ DV extraction ------------------ */ typedef struct FrameMeta { u8 key; } FrameMeta; /* Extracts video ES + audio Ogg from DV!! file. Handles both with/without preroll block. */ static void dv_extract(const u8* dv, u32 dv_len, u32* out_w, u32* out_h, u32* out_frames, u32* out_channels, u8** out_video_es, u32* out_video_es_len, u8** out_audio_ogg, u32* out_audio_ogg_len, FrameMeta** out_meta) { u32 w, h, frames2, channels, asize1st; u32 table_off, table_len, preroll_off; u32 i; u64 sum_total; u32 data_off_with_preroll, data_off_no_preroll, data_off; int has_preroll; Buf video, audio; FrameMeta* meta; u32 pos; if (dv_len < 0x4C) die("Input too small for DV!! header"); if (rd32le(dv + 0) != MAGIC_DV_LE) die("Bad DV!! signature"); w = rd32le(dv + 16); h = rd32le(dv + 20); frames2 = rd32le(dv + 28); channels = rd32le(dv + 32); asize1st = rd32le(dv + 36); table_off = 0x4C; table_len = frames2 * 0x14UL; preroll_off = table_off + table_len; if (dv_len < preroll_off) die("Truncated: frame table"); /* Compute exact byte sum of all packets */ sum_total = 0; for (i = 0; i < frames2; i++) { u32 base = table_off + i * 0x14UL; u32 fsize = rd32le(dv + base + 0); sum_total += (u64)(fsize & 0x7FFFFFFFUL); } data_off_no_preroll = preroll_off; data_off_with_preroll = preroll_off + PREROLL_BLOCK_SIZE; /* Prefer strict size match. */ if ((u64)data_off_with_preroll + sum_total == (u64)dv_len) has_preroll = 1; else if ((u64)data_off_no_preroll + sum_total == (u64)dv_len) has_preroll = 0; else { /* Fallback: if file doesn't match exactly, pick the variant that fits and warn. */ if (data_off_with_preroll < dv_len && (u64)data_off_with_preroll + sum_total < (u64)dv_len) { fprintf(stderr, "WARNING: size mismatch; assuming preroll block present.\n"); has_preroll = 1; } else if ((u64)data_off_no_preroll + sum_total < (u64)dv_len) { fprintf(stderr, "WARNING: size mismatch; assuming no preroll block.\n"); has_preroll = 0; } else { die("Cannot determine payload offset (size mismatch too large)"); } } data_off = has_preroll ? data_off_with_preroll : data_off_no_preroll; /* Sanitize preroll length */ if (channels == 0) asize1st = 0; else { if (asize1st == 0xFFFFFFFFUL) asize1st = 0; if (asize1st > PREROLL_BLOCK_SIZE) { fprintf(stderr, "WARNING: asize1st > 0xC000; clamping.\n"); asize1st = PREROLL_BLOCK_SIZE; } } buf_init(&video); buf_init(&audio); /* Copy meaningful preroll audio bytes */ if (channels != 0 && has_preroll && asize1st) { if (preroll_off + asize1st > dv_len) die("Truncated: preroll audio"); buf_append(&audio, dv + preroll_off, asize1st); } meta = (FrameMeta*)calloc((size_t)frames2, sizeof(FrameMeta)); if (!meta) die("Out of memory"); pos = data_off; for (i = 0; i < frames2; i++) { u32 base = table_off + i * 0x14UL; u32 fsize = rd32le(dv + base + 0); u32 vsize = rd32le(dv + base + 4); u32 dsize = rd32le(dv + base + 12); u32 total = (fsize & 0x7FFFFFFFUL); const u8* pkt; meta[i].key = (u8)((fsize & 0x80000000UL) ? 1 : 0); if ((u64)pos + (u64)total > (u64)dv_len) die("Truncated: frame data"); pkt = dv + pos; if (channels == 0) { /* video-only */ if (total) buf_append(&video, pkt, total); pos += total; continue; } /* audio+video */ if (total < vsize) die("Corrupt item: total < vsize"); /* Packet layout is strictly: * video[vsize] + audio[asize] + padding[data] * where: * tail = total - vsize * asize = min(dsize, tail) * data = tail - asize * * So audio is read immediately after video, and any remaining bytes are padding/alignment. */ if (vsize) buf_append(&video, pkt, vsize); if (total > vsize) { u32 tail = total - vsize; u32 audio_size = (dsize <= tail) ? dsize : tail; /* u32 padding_size = tail - audio_size; */ if (audio_size) { buf_append(&audio, pkt + vsize, audio_size); } /* padding bytes, if any, are intentionally skipped */ } pos += total; } *out_w = w; *out_h = h; *out_frames = frames2; *out_channels = channels; *out_video_es = video.p; *out_video_es_len = video.len; *out_audio_ogg = audio.p; *out_audio_ogg_len = audio.len; *out_meta = meta; } /* ------------------ MPEG-4 Part 2 frame splitting ------------------ */ typedef struct Span { u8* p; u32 len; } Span; /* Splits ES by VOP start codes (00 00 01 B6). * Prepends global header (everything before first VOP) to the first frame. */ static Span* mpeg4_split_vop_frames(const u8* es, u32 es_len, u32* out_count) { u32* idxs; u32 cap, count, i; Span* frames; u32 header_len; u8* header; cap = 1024; count = 0; idxs = (u32*)malloc((size_t)cap * sizeof(u32)); if (!idxs) die("Out of memory"); i = 0; while (i + 4 <= es_len) { u32 j; int found; found = 0; for (j = i; j + 4 <= es_len; j++) { if (es[j]==0x00 && es[j+1]==0x00 && es[j+2]==0x01 && es[j+3]==0xB6) { found = 1; break; } } if (!found) break; if (count >= cap) { u32* np; cap = cap + cap/2 + 256; np = (u32*)realloc(idxs, (size_t)cap * sizeof(u32)); if (!np) die("Out of memory"); idxs = np; } idxs[count++] = j; i = j + 4; } if (count == 0) { free(idxs); die("No MPEG-4 VOP start codes found"); } header_len = idxs[0]; header = 0; if (header_len) { header = (u8*)malloc((size_t)header_len); if (!header) die("Out of memory"); memcpy(header, es, (size_t)header_len); } frames = (Span*)calloc((size_t)count, sizeof(Span)); if (!frames) die("Out of memory"); for (i = 0; i < count; i++) { u32 start, end, flen; start = idxs[i]; end = (i + 1 < count) ? idxs[i+1] : es_len; flen = end - start; if (i == 0 && header_len) { u32 total = header_len + flen; frames[i].p = (u8*)malloc((size_t)total); if (!frames[i].p) die("Out of memory"); memcpy(frames[i].p, header, (size_t)header_len); memcpy(frames[i].p + header_len, es + start, (size_t)flen); frames[i].len = total; } else { frames[i].p = (u8*)malloc((size_t)flen); if (!frames[i].p) die("Out of memory"); memcpy(frames[i].p, es + start, (size_t)flen); frames[i].len = flen; } } if (header) free(header); free(idxs); *out_count = count; return frames; } static void free_frames(Span* frames, u32 count) { u32 i; if (!frames) return; for (i = 0; i < count; i++) if (frames[i].p) free(frames[i].p); free(frames); } /* ------------------ OGM packet builders ------------------ */ /* Builds 52-byte OGM stream_header for video (subtype "XVID"). */ static void ogm_build_stream_header_video(u8 out52[52], int w, int h, int fps) { double tu_d; u64 time_unit; memset(out52, 0, 52); memcpy(out52 + 0, "video", 5); memcpy(out52 + 8, "XVID", 4); wr32le(out52 + 12, 52); tu_d = 10000000.0 / (double)fps; time_unit = (u64)(tu_d + 0.5); wr64le(out52 + 16, time_unit); wr64le(out52 + 24, (u64)1); wr32le(out52 + 32, 1); wr32le(out52 + 36, 0); wr32le(out52 + 44, (u32)w); wr32le(out52 + 48, (u32)h); } /* Creates OGM video BOS packet: 0x01 + stream_header. */ static void ogm_make_video_header_pkt(Buf* pkt, int w, int h, int fps) { u8 sh[52]; buf_init(pkt); ogm_build_stream_header_video(sh, w, h, fps); buf_append_u8(pkt, 0x01); buf_append(pkt, sh, 52); } /* Creates OGM comment packet: 0x03 + vendor + no user comments (Vorbis-style comment payload). */ static void ogm_make_video_comment_pkt(Buf* pkt) { const char* vendor = "dvbang"; u32 vlen; u8 tmp[4]; vlen = (u32)strlen(vendor); buf_init(pkt); buf_append_u8(pkt, 0x03); wr32le(tmp, vlen); buf_append(pkt, tmp, 4); buf_append(pkt, (const u8*)vendor, vlen); wr32le(tmp, 0); buf_append(pkt, tmp, 4); } /* Creates one OGM video data packet: * flags: lenbytes=2 (0x80), keyframe (0x08 optionally) * duration samples: 1 (LE16) * payload: MPEG4 frame bytes */ static void ogm_make_video_data_pkt(Buf* pkt, const u8* frame, u32 frame_len, u8 is_key) { u8 flags; buf_init(pkt); flags = (u8)(0x80 | (is_key ? 0x08 : 0x00)); buf_append_u8(pkt, flags); buf_append_u8(pkt, 1); buf_append_u8(pkt, 0); buf_append(pkt, frame, frame_len); } /* ------------------ Mux (writes .ogv) ------------------ */ static void mux_ogv(const char* out_path, const OggPage* audio_pages, u32 audio_page_count, u32 sample_rate, int w, int h, int fps, const Span* frames, const FrameMeta* meta, u32 frame_count, int has_audio) { FILE* fo; Buf out; u32 video_serial, audio_serial; u32 hdr_end; u32 vseq, v_i; double fps_d, half_frame; Buf pkt; u32 ai; buf_init(&out); video_serial = VIDEO_SERIAL_FIXED; if (has_audio) { if (audio_page_count == 0) die("No audio pages"); audio_serial = audio_pages[0].serial; if (video_serial == audio_serial) video_serial ^= 0xFFFFFFFFUL; hdr_end = vorbis_header_end_page_index(audio_pages, audio_page_count); /* Copy Vorbis BOS page unchanged */ buf_append(&out, audio_pages[0].bytes, audio_pages[0].bytes_len); /* Video BOS page */ vseq = 0; ogm_make_video_header_pkt(&pkt, w, h, fps); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (u64)0, (u8)0x02, (u8)0x00); buf_free(&pkt); /* Video comment page */ ogm_make_video_comment_pkt(&pkt); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (u64)0, (u8)0x00, (u8)0x00); buf_free(&pkt); /* Copy remaining Vorbis header pages unchanged */ for (ai = 1; ai <= hdr_end && ai < audio_page_count; ai++) { buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len); } fps_d = (double)fps; half_frame = 0.5 / fps_d; v_i = 0; /* Interleave by audio page timestamps */ for (ai = hdr_end + 1; ai < audio_page_count; ai++) { double at; at = 0.0; if (sample_rate) at = (double)audio_pages[ai].granule / (double)sample_rate; while (v_i < frame_count) { double vt_next; u8 eos_flag; u64 gran; vt_next = (double)(v_i + 1) / fps_d; if (vt_next <= at + half_frame) { eos_flag = (u8)((v_i + 1 == frame_count) ? 0x04 : 0x00); gran = (u64)(v_i + 1); ogm_make_video_data_pkt(&pkt, frames[v_i].p, frames[v_i].len, meta ? meta[v_i].key : 1); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, gran, (u8)0x00, eos_flag); buf_free(&pkt); v_i++; } else { break; } } /* Copy current audio page unchanged */ buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len); } } else { /* No audio: write only video stream (still valid Ogg/OGM). */ vseq = 0; ogm_make_video_header_pkt(&pkt, w, h, fps); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (u64)0, (u8)0x02, (u8)0x00); buf_free(&pkt); ogm_make_video_comment_pkt(&pkt); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (u64)0, (u8)0x00, (u8)0x00); buf_free(&pkt); v_i = 0; } /* Flush remaining video frames (if any) */ while (v_i < frame_count) { u8 eos_flag; u64 gran; eos_flag = (u8)((v_i + 1 == frame_count) ? 0x04 : 0x00); gran = (u64)(v_i + 1); ogm_make_video_data_pkt(&pkt, frames[v_i].p, frames[v_i].len, meta ? meta[v_i].key : 1); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, gran, (u8)0x00, eos_flag); buf_free(&pkt); v_i++; } /* Write to disk */ fo = fopen(out_path, "wb"); if (!fo) die("Cannot open output file"); if (out.len && fwrite(out.p, 1, (size_t)out.len, fo) != (size_t)out.len) { fclose(fo); die("Write failed"); } fclose(fo); buf_free(&out); } /* ------------------ CLI helpers ------------------ */ static int streq(const char* a, const char* B) { return strcmp(a, B) == 0; } static void usage(const char* exe) { fprintf(stderr, "Usage:\n" " %s <input.dv> [output.ogv] [--fps N] [--validate]\n\n" "Options:\n" " --fps N set fps (default 25)\n" " --validate run ffmpeg decode test (optional; ffmpeg must be in PATH)\n" " -h,--help show help\n", exe ); } static void make_default_out(const char* in, char* out, u32 outsz) { const char* dot; u32 n; dot = strrchr(in, '.'); if (!dot) { snprintf(out, outsz, "%s.ogv", in); return; } n = (u32)(dot - in); if (n + 5 >= outsz) die("Output path too long"); memcpy(out, in, (size_t)n); memcpy(out + n, ".ogv", 5); } /* ------------------ main ------------------ */ int main(int argc, char** argv) { const char* in_path; const char* out_arg; char out_path[1024]; int validate; int fps; u8* dv; u32 dv_len; u32 w, h, frames2, channels; u8* video_es; u32 video_es_len; u8* audio_ogg; u32 audio_ogg_len; FrameMeta* meta; OggPage* audio_pages; u32 audio_page_count; u32 sample_rate; Span* frames; u32 frame_count; int i; int positional; /* defaults */ in_path = 0; out_arg = 0; validate = 0; fps = 25; if (argc < 2) { usage(argv[0]); return 2; } positional = 0; for (i = 1; i < argc; i++) { const char* a = argv[i]; if (streq(a, "-h") || streq(a, "--help")) { usage(argv[0]); return 0; } if (streq(a, "--validate")) { validate = 1; continue; } if (streq(a, "--fps")) { if (i + 1 >= argc) { fprintf(stderr, "Missing value for --fps\n\n"); usage(argv[0]); return 2; } fps = atoi(argv[++i]); if (fps <= 0 || fps > 240) { fprintf(stderr, "Invalid fps: %d\n", fps); return 2; } continue; } if (a[0] == '-') { fprintf(stderr, "Unknown option: %s\n\n", a); usage(argv[0]); return 2; } if (positional == 0) { in_path = a; positional++; continue; } if (positional == 1) { out_arg = a; positional++; continue; } fprintf(stderr, "Too many arguments.\n\n"); usage(argv[0]); return 2; } if (!in_path) { usage(argv[0]); return 2; } if (out_arg) { strncpy(out_path, out_arg, sizeof(out_path) - 1); out_path[sizeof(out_path) - 1] = 0; } else { make_default_out(in_path, out_path, (u32)sizeof(out_path)); } crc_table_init(); dv = read_file_all(in_path, &dv_len); if (!dv) die2("Cannot read input file: ", in_path); /* Extract */ w = h = frames2 = channels = 0; video_es = 0; video_es_len = 0; audio_ogg = 0; audio_ogg_len = 0; meta = 0; dv_extract(dv, dv_len, &w, &h, &frames2, &channels, &video_es, &video_es_len, &audio_ogg, &audio_ogg_len, &meta); /* Split frames */ frames = mpeg4_split_vop_frames(video_es, video_es_len, &frame_count); /* Parse audio if present */ audio_pages = 0; audio_page_count = 0; sample_rate = 0; if (channels != 0) { audio_pages = ogg_parse_pages(audio_ogg, audio_ogg_len, &audio_page_count); if (!audio_pages || audio_page_count == 0) die("Audio parse failed: no Ogg pages found"); sample_rate = vorbis_detect_sample_rate(audio_pages, audio_page_count); if (sample_rate == 0) { fprintf(stderr, "WARNING: could not detect Vorbis sample rate; assuming 44100.\n"); sample_rate = 44100; } } /* Mux */ mux_ogv(out_path, audio_pages, audio_page_count, sample_rate, (int)w, (int)h, fps, frames, meta, frame_count, (channels != 0)); printf("ogv: %s\n", out_path); printf("video: MPEG-4 Part 2 (%ux%u, frames=%u, fps=%d)\n", w, h, frame_count, fps); if (channels != 0) printf("audio: Vorbis (sr=%u, pages=%u)\n", sample_rate, audio_page_count); else printf("audio: none\n"); /* Optional validation */ if (validate) { char cmd[1400]; int rc; snprintf(cmd, sizeof(cmd), "ffmpeg -hide_banner -loglevel error -i \"%s\" -f null -", out_path); rc = system(cmd); if (rc != 0) { fprintf(stderr, "WARNING: ffmpeg validation failed (ffmpeg missing or decode errors).\n"); } } /* cleanup */ if (dv) free(dv); if (video_es) free(video_es); if (audio_ogg) free(audio_ogg); if (audio_pages) ogg_free_pages(audio_pages, audio_page_count); if (frames) free_frames(frames, frame_count); if (meta) free(meta); return 0; } Спасибо сказали:
|
| Lumis |
Mar 6 2026, 22:59
Сообщение
#11
|
|
Member ![]() ![]() Группа: Authorized Сообщений: 11 Регистрация: 2-March 26 Пользователь №: 18,038 Спасибо сказали: 11 раз(а) |
Есть надежда, что мы подошли к финалу разбора, так что выдаю версию со стандартными целочисленными типами, чтобы ничего не надо было менять. Если нет, то удалю пост и подумаем ещё.
На моем тестовом сете все чисто. CODE /* dvbang2ogv.c * ANSI C / C89, Visual Studio 2008 friendly * License: MIT, Copyright 2026 Lumis * v1.5 uses stdint.h * * Converts proprietary "DV!!" .dv container into .ogv (OGM). * Video: MPEG-4 Part 2 (VOP start code 00 00 01 B6) kept byte-exact. * Audio: Ogg/Vorbis stream bytes kept byte-exact (no re-encode). * * Container format (as reverse engineered): * * dvf_head (0x4C bytes, little-endian u32 fields): * magic = "DV!!" (0x21215644 LE) * width = hdr[4] * height = hdr[5] * frames2 = hdr[7] (count of items/frames) * channels = hdr[8] (0 no audio, 2 stereo) * asize1st = hdr[9] (used bytes in preroll area; rest is zero padding) * * then: item table: frames2 * 0x14 bytes: * fsize: total packet bytes, with keyframe flag in bit31 * vsize: video bytes inside packet * zero1 * dsize: audio bytes inside packet (in many files dsize == total - vsize) * zero2 * * then: preroll audio region of 0xC000 bytes may exist (often padded with zeros); * only first asize1st bytes are meaningful audio bytes. * * then: frames2 packets of (fsize & 0x7fffffff) bytes. * packet layout: * video[vsize] + blob[blob_size] + audio[audio_size] * * where: * tail = total - vsize * audio_size= min(dsize, tail) <-- critical fix (53-99.dv needs this) * blob_size = tail - audio_size * * OGV/OGM mux: * - Copy Vorbis BOS page unchanged (byte-exact) * - Create OGM video BOS page (stream_header, subtype "XVID") * - Create OGM video comment page * - Copy remaining Vorbis header pages unchanged (until 3 Vorbis packets end) * - Interleave: * at = audio_page.granule / sample_rate * emit video frames while ((v_i+1)/fps) <= at + (0.5/fps) * then copy audio page * Finally flush remaining video frames. * * Determinism: * - video serial hardcoded to 0xC0DEC0DE (xor if equals audio serial). * * Build (small-ish EXE): * cl /O1 /Os /GL /GS- /Gy /GR- /EHsc- /MD dvbang2ogv.c /link /LTCG /OPT:REF /OPT:ICF /SUBSYSTEM:CONSOLE,5.01 */ #include <stdio.h> #include <stdlib.h> #include <string.h> /* ----- Standard integer types where available ----- */ #if defined(_MSC_VER) && (_MSC_VER < 1600) typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; typedef unsigned __int64 uint64_t; #else #include <stdint.h> #endif #ifdef _MSC_VER #ifndef snprintf #define snprintf _snprintf #endif #endif #define MAGIC_DV_LE 0x21215644UL /* "DV!!" LE */ #define PREROLL_BLOCK_SIZE 0xC000UL #define VIDEO_SERIAL_FIXED 0xC0DEC0DEUL static void die(const char* msg) { fprintf(stderr, "ERROR: %s\n", msg); exit(2); } static void die2(const char* msg, const char* more) { fprintf(stderr, "ERROR: %s%s\n", msg, more ? more : ""); exit(2); } /* LE helpers */ static uint32_t rd32le(const uint8_t* p){ return (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24); } static uint64_t rd64le(const uint8_t* p){ uint64_t v=0; v |= (uint64_t)p[0]; v |= (uint64_t)p[1]<<8; v |= (uint64_t)p[2]<<16; v |= (uint64_t)p[3]<<24; v |= (uint64_t)p[4]<<32; v |= (uint64_t)p[5]<<40; v |= (uint64_t)p[6]<<48; v |= (uint64_t)p[7]<<56; return v; } static void wr32le(uint8_t* p, uint32_t v){ p[0]=(uint8_t)v; p[1]=(uint8_t)(v>>8); p[2]=(uint8_t)(v>>16); p[3]=(uint8_t)(v>>24); } static void wr64le(uint8_t* p, uint64_t v){ p[0]=(uint8_t)v; p[1]=(uint8_t)(v>>8); p[2]=(uint8_t)(v>>16); p[3]=(uint8_t)(v>>24); p[4]=(uint8_t)(v>>32); p[5]=(uint8_t)(v>>40); p[6]=(uint8_t)(v>>48); p[7]=(uint8_t)(v>>56); } /* Buf */ typedef struct Buf { uint8_t* p; uint32_t len; uint32_t cap; } Buf; static void buf_init(Buf* B){ b->p=0; b->len=0; b->cap=0; } static void buf_reserve(Buf* b, uint32_t need){ uint32_t ncap; uint8_t* np; if (need <= b->cap) return; ncap = b->cap ? (b->cap + (b->cap>>1) + 64) : 256; if (ncap < need) ncap = need; np = (uint8_t*)realloc(b->p, (size_t)ncap); if (!np) die("Out of memory"); b->p=np; b->cap=ncap; } static void buf_append(Buf* b, const uint8_t* data, uint32_t n){ if (!n) return; buf_reserve(b, b->len + n); memcpy(b->p + b->len, data, (size_t)n); b->len += n; } static void buf_append_u8(Buf* b, uint8_t v){ buf_reserve(b, b->len + 1); b->p[b->len++] = v; } static void buf_free(Buf* B){ if (b->p) free(b->p); b->p=0; b->len=0; b->cap=0; } /* Read file */ static uint8_t* read_file_all(const char* path, uint32_t* out_len){ FILE* f; long sz; uint8_t* data; size_t got; f = fopen(path, "rb"); if (!f) return 0; fseek(f, 0, SEEK_END); sz = ftell(f); if (sz < 0) { fclose(f); return 0; } fseek(f, 0, SEEK_SET); data = (uint8_t*)malloc((size_t)sz); if (!data) { fclose(f); return 0; } got = (sz>0) ? fread(data, 1, (size_t)sz, f) : 0; fclose(f); if ((long)got != sz) { free(data); return 0; } *out_len = (uint32_t)sz; return data; } /* Ogg CRC */ static uint32_t CRC_TABLE[256]; static void crc_table_init(void){ uint32_t poly=0x04C11DB7UL, i, j, r; for (i=0;i<256;i++){ r = i<<24; for (j=0;j<8;j++){ if (r & 0x80000000UL) r = (r<<1) ^ poly; else r <<= 1; } CRC_TABLE[i]=r; } } static uint32_t ogg_crc(const uint8_t* page, uint32_t len){ uint32_t crc=0, i; for (i=0;i<len;i++){ uint8_t b=page[i]; crc = (crc<<8) ^ CRC_TABLE[((crc>>24) ^ B) & 0xFF]; } return crc; } /* Ogg page writer */ static void ogg_build_page_raw(Buf* out, const uint8_t* segs, uint32_t seg_count, const uint8_t* body, uint32_t body_len, uint32_t serial, uint32_t seq, uint64_t granule, uint8_t header_type) { uint8_t hdr[27]; Buf page; uint32_t crc; memset(hdr, 0, sizeof(hdr)); hdr[0]='O'; hdr[1]='g'; hdr[2]='g'; hdr[3]='S'; hdr[4]=0; hdr[5]=header_type; wr64le(hdr+6, granule); wr32le(hdr+14, serial); wr32le(hdr+18, seq); wr32le(hdr+22, 0); hdr[26]=(uint8_t)seg_count; buf_init(&page); buf_append(&page, hdr, 27); buf_append(&page, segs, seg_count); buf_append(&page, body, body_len); crc = ogg_crc(page.p, page.len); wr32le(page.p + 22, crc); buf_append(out, page.p, page.len); buf_free(&page); } /* Packet->pages splitting (fixes 255 segments limit) */ static void ogg_write_packet_split(Buf* out, const uint8_t* pkt, uint32_t pkt_len, uint32_t serial, uint32_t* io_seq, uint64_t granule_last, uint8_t header_type_first, uint8_t header_type_last) { uint32_t pos=0, seq=*io_seq; const uint32_t max_body = 255UL*255UL; while (pos < pkt_len){ uint32_t rem = pkt_len - pos; if (rem <= max_body){ uint8_t segs[255]; uint32_t seg_count=0; uint32_t n=rem; uint8_t ht; while (n >= 255){ segs[seg_count++] = 255; n -= 255; } segs[seg_count++] = (uint8_t)n; ht = (pos==0) ? (uint8_t)(header_type_first | header_type_last) : (uint8_t)(0x01 | header_type_last); ogg_build_page_raw(out, segs, seg_count, pkt + pos, rem, serial, seq, granule_last, ht); seq++; break; } else { uint8_t segs_full[255]; uint32_t i; uint8_t ht; for (i=0;i<255;i++) segs_full[i]=255; ht = (pos==0) ? header_type_first : 0x01; ogg_build_page_raw(out, segs_full, 255, pkt + pos, max_body, serial, seq, 0, ht); seq++; pos += max_body; } } *io_seq = seq; } /* Ogg page parse (audio only, for timestamps + raw copy) */ typedef struct OggPage { uint8_t header_type; uint64_t granule; uint32_t serial; uint32_t seq; uint8_t* segtable; uint32_t segtable_len; uint8_t* body; uint32_t body_len; uint8_t* bytes; uint32_t bytes_len; } OggPage; static OggPage* ogg_parse_pages(const uint8_t* data, uint32_t len, uint32_t* out_count){ OggPage* pages; uint32_t cap=64, count=0, i=0; pages = (OggPage*)calloc((size_t)cap, sizeof(OggPage)); if (!pages) die("Out of memory"); while (1){ uint32_t j,k,seg_off,body_off,body_len,end; int found; uint8_t nseg; found=0; for (j=i; j+4<=len; j++){ if (data[j]=='O' && data[j+1]=='g' && data[j+2]=='g' && data[j+3]=='S'){ found=1; break; } } if (!found) break; if (j+27 > len) break; nseg = data[j+26]; seg_off = j+27; body_off = seg_off + (uint32_t)nseg; if (body_off > len) break; body_len=0; for (k=0;k<(uint32_t)nseg;k++) body_len += data[seg_off+k]; end = body_off + body_len; if (end > len) break; if (count >= cap){ OggPage* np; cap = cap + cap/2 + 32; np = (OggPage*)realloc(pages, (size_t)cap*sizeof(OggPage)); if (!np) die("Out of memory"); pages=np; memset(pages+count, 0, (size_t)(cap-count)*sizeof(OggPage)); } pages[count].header_type = data[j+5]; pages[count].granule = rd64le(data + j + 6); pages[count].serial = rd32le(data + j + 14); pages[count].seq = rd32le(data + j + 18); pages[count].segtable_len = (uint32_t)nseg; pages[count].segtable = (uint8_t*)malloc((size_t)nseg); pages[count].body = (uint8_t*)malloc((size_t)body_len); pages[count].bytes_len = end - j; pages[count].bytes = (uint8_t*)malloc((size_t)pages[count].bytes_len); if (!pages[count].segtable || !pages[count].body || !pages[count].bytes) die("Out of memory"); memcpy(pages[count].segtable, data + seg_off, (size_t)nseg); memcpy(pages[count].body, data + body_off, (size_t)body_len); memcpy(pages[count].bytes, data + j, (size_t)pages[count].bytes_len); pages[count].body_len = body_len; count++; i = end; } *out_count = count; return pages; } static void ogg_free_pages(OggPage* pages, uint32_t count){ uint32_t i; if (!pages) return; for (i=0;i<count;i++){ if (pages[i].segtable) free(pages[i].segtable); if (pages[i].body) free(pages[i].body); if (pages[i].bytes) free(pages[i].bytes); } free(pages); } /* Vorbis sample rate detection */ static uint32_t vorbis_detect_sample_rate(const OggPage* pages, uint32_t count){ Buf cur; uint32_t i,sidx; buf_init(&cur); for (i=0;i<count;i++){ uint32_t off=0; for (sidx=0;sidx<pages[i].segtable_len;sidx++){ uint8_t s = pages[i].segtable[sidx]; buf_append(&cur, pages[i].body + off, (uint32_t)s); off += (uint32_t)s; if (s < 255){ if (cur.len >= 16 && cur.p[0]==0x01 && cur.p[1]=='v' && cur.p[2]=='o' && cur.p[3]=='r' && cur.p[4]=='b' && cur.p[5]=='i' && cur.p[6]=='s') { uint32_t sr = rd32le(cur.p + 12); buf_free(&cur); return sr; } cur.len = 0; } } } buf_free(&cur); return 0; } static uint32_t vorbis_header_end_page_index(const OggPage* pages, uint32_t count){ uint32_t pkt=0,i,s; for (i=0;i<count;i++){ for (s=0;s<pages[i].segtable_len;s++){ if (pages[i].segtable[s] < 255){ pkt++; if (pkt >= 3) return i; } } } return count ? (count-1) : 0; } /* DV extraction */ typedef struct FrameMeta { uint8_t key; } FrameMeta; static void dv_extract(const uint8_t* dv, uint32_t dv_len, uint32_t* out_w, uint32_t* out_h, uint32_t* out_frames, uint32_t* out_channels, uint8_t** out_video_es, uint32_t* out_video_es_len, uint8_t** out_audio_ogg, uint32_t* out_audio_ogg_len, FrameMeta** out_meta) { uint32_t w, h, frames2, channels, asize1st; uint32_t table_off, table_len, preroll_off; uint32_t i; uint64_t sum_total; uint32_t data_off_with_preroll, data_off_no_preroll, data_off; int has_preroll; Buf video, audio; FrameMeta* meta; uint32_t pos; if (dv_len < 0x4C) die("Input too small for DV!! header"); if (rd32le(dv + 0) != MAGIC_DV_LE) die("Bad DV!! signature"); w = rd32le(dv + 16); h = rd32le(dv + 20); frames2 = rd32le(dv + 28); channels = rd32le(dv + 32); asize1st = rd32le(dv + 36); table_off = 0x4C; table_len = frames2 * 0x14UL; preroll_off = table_off + table_len; if (dv_len < preroll_off) die("Truncated: frame table"); sum_total = 0; for (i=0;i<frames2;i++){ uint32_t base = table_off + i * 0x14UL; uint32_t fsize = rd32le(dv + base + 0); sum_total += (uint64_t)(fsize & 0x7FFFFFFFUL); } data_off_no_preroll = preroll_off; data_off_with_preroll = preroll_off + PREROLL_BLOCK_SIZE; if ((uint64_t)data_off_with_preroll + sum_total == (uint64_t)dv_len) has_preroll = 1; else if ((uint64_t)data_off_no_preroll + sum_total == (uint64_t)dv_len) has_preroll = 0; else { if (data_off_with_preroll < dv_len && (uint64_t)data_off_with_preroll + sum_total < (uint64_t)dv_len) { fprintf(stderr, "WARNING: size mismatch; assuming preroll block present.\n"); has_preroll = 1; } else if ((uint64_t)data_off_no_preroll + sum_total < (uint64_t)dv_len) { fprintf(stderr, "WARNING: size mismatch; assuming no preroll block.\n"); has_preroll = 0; } else { die("Cannot determine payload offset (size mismatch too large)"); } } data_off = has_preroll ? data_off_with_preroll : data_off_no_preroll; if (channels == 0) asize1st = 0; else { if (asize1st == 0xFFFFFFFFUL) asize1st = 0; if (asize1st > PREROLL_BLOCK_SIZE) { fprintf(stderr, "WARNING: asize1st > 0xC000; clamping.\n"); asize1st = PREROLL_BLOCK_SIZE; } } buf_init(&video); buf_init(&audio); if (channels != 0 && has_preroll && asize1st) { if (preroll_off + asize1st > dv_len) die("Truncated: preroll audio"); buf_append(&audio, dv + preroll_off, asize1st); } meta = (FrameMeta*)calloc((size_t)frames2, sizeof(FrameMeta)); if (!meta) die("Out of memory"); pos = data_off; for (i=0;i<frames2;i++){ uint32_t base = table_off + i * 0x14UL; uint32_t fsize = rd32le(dv + base + 0); uint32_t vsize = rd32le(dv + base + 4); uint32_t dsize = rd32le(dv + base + 12); uint32_t total = (fsize & 0x7FFFFFFFUL); const uint8_t* pkt = dv + pos; meta[i].key = (uint8_t)((fsize & 0x80000000UL) ? 1 : 0); if ((uint64_t)pos + (uint64_t)total > (uint64_t)dv_len) die("Truncated: frame data"); if (channels == 0) { if (total) buf_append(&video, pkt, total); pos += total; continue; } if (total < vsize) die("Corrupt item: total < vsize"); /* Strict packet layout: * video[vsize] + audio[min(dsize, total-vsize)] + padding */ if (vsize) buf_append(&video, pkt, vsize); if (total > vsize) { uint32_t tail = total - vsize; uint32_t audio_size = (dsize <= tail) ? dsize : tail; /* uint32_t padding_size = tail - audio_size; */ if (audio_size) { buf_append(&audio, pkt + vsize, audio_size); } /* padding is skipped */ } pos += total; } *out_w = w; *out_h = h; *out_frames = frames2; *out_channels = channels; *out_video_es = video.p; *out_video_es_len = video.len; *out_audio_ogg = audio.p; *out_audio_ogg_len = audio.len; *out_meta = meta; } /* MPEG-4 VOP split */ typedef struct Span { uint8_t* p; uint32_t len; } Span; static Span* mpeg4_split_vop_frames(const uint8_t* es, uint32_t es_len, uint32_t* out_count){ uint32_t* idxs; uint32_t cap=1024, count=0, i; Span* frames; uint32_t header_len; uint8_t* header=0; idxs = (uint32_t*)malloc((size_t)cap*sizeof(uint32_t)); if (!idxs) die("Out of memory"); i=0; while (i+4 <= es_len){ uint32_t j; int found=0; for (j=i; j+4<=es_len; j++){ if (es[j]==0x00 && es[j+1]==0x00 && es[j+2]==0x01 && es[j+3]==0xB6){ found=1; break; } } if (!found) break; if (count >= cap){ uint32_t* np; cap = cap + cap/2 + 256; np = (uint32_t*)realloc(idxs, (size_t)cap*sizeof(uint32_t)); if (!np) die("Out of memory"); idxs=np; } idxs[count++] = j; i = j + 4; } if (count==0){ free(idxs); die("No MPEG-4 VOP start codes found"); } header_len = idxs[0]; if (header_len){ header = (uint8_t*)malloc((size_t)header_len); if (!header) die("Out of memory"); memcpy(header, es, (size_t)header_len); } frames = (Span*)calloc((size_t)count, sizeof(Span)); if (!frames) die("Out of memory"); for (i=0;i<count;i++){ uint32_t start=idxs[i]; uint32_t end=(i+1<count)?idxs[i+1]:es_len; uint32_t flen=end-start; if (i==0 && header_len){ uint32_t total=header_len+flen; frames[i].p=(uint8_t*)malloc((size_t)total); if (!frames[i].p) die("Out of memory"); memcpy(frames[i].p, header, (size_t)header_len); memcpy(frames[i].p+header_len, es+start, (size_t)flen); frames[i].len=total; } else { frames[i].p=(uint8_t*)malloc((size_t)flen); if (!frames[i].p) die("Out of memory"); memcpy(frames[i].p, es+start, (size_t)flen); frames[i].len=flen; } } if (header) free(header); free(idxs); *out_count=count; return frames; } static void free_frames(Span* frames, uint32_t count){ uint32_t i; if (!frames) return; for (i=0;i<count;i++) if (frames[i].p) free(frames[i].p); free(frames); } /* OGM packet builders */ static void ogm_build_stream_header_video(uint8_t out52[52], int w, int h, int fps){ double tu_d; uint64_t time_unit; memset(out52, 0, 52); memcpy(out52 + 0, "video", 5); memcpy(out52 + 8, "XVID", 4); wr32le(out52 + 12, 52); tu_d = 10000000.0 / (double)fps; time_unit = (uint64_t)(tu_d + 0.5); wr64le(out52 + 16, time_unit); wr64le(out52 + 24, (uint64_t)1); wr32le(out52 + 32, 1); wr32le(out52 + 36, 0); wr32le(out52 + 44, (uint32_t)w); wr32le(out52 + 48, (uint32_t)h); } static void ogm_make_video_header_pkt(Buf* pkt, int w, int h, int fps){ uint8_t sh[52]; buf_init(pkt); ogm_build_stream_header_video(sh, w, h, fps); buf_append_u8(pkt, 0x01); buf_append(pkt, sh, 52); } static void ogm_make_video_comment_pkt(Buf* pkt){ const char* vendor="dvbang"; uint32_t vlen=(uint32_t)strlen(vendor); uint8_t tmp[4]; buf_init(pkt); buf_append_u8(pkt, 0x03); wr32le(tmp, vlen); buf_append(pkt, tmp, 4); buf_append(pkt, (const uint8_t*)vendor, vlen); wr32le(tmp, 0); buf_append(pkt, tmp, 4); } static void ogm_make_video_data_pkt(Buf* pkt, const uint8_t* frame, uint32_t frame_len, uint8_t is_key){ uint8_t flags = (uint8_t)(0x80 | (is_key ? 0x08 : 0x00)); buf_init(pkt); buf_append_u8(pkt, flags); buf_append_u8(pkt, 1); buf_append_u8(pkt, 0); buf_append(pkt, frame, frame_len); } /* mux */ static void mux_ogv(const char* out_path, const OggPage* audio_pages, uint32_t audio_page_count, uint32_t sample_rate, int w, int h, int fps, const Span* frames, const FrameMeta* meta, uint32_t frame_count, int has_audio) { FILE* fo; Buf out, pkt; uint32_t video_serial = VIDEO_SERIAL_FIXED; uint32_t vseq = 0; uint32_t v_i = 0; buf_init(&out); if (has_audio) { uint32_t audio_serial, hdr_end, ai; double fps_d, half_frame; if (audio_page_count == 0) die("No audio pages"); audio_serial = audio_pages[0].serial; if (video_serial == audio_serial) video_serial ^= 0xFFFFFFFFUL; hdr_end = vorbis_header_end_page_index(audio_pages, audio_page_count); buf_append(&out, audio_pages[0].bytes, audio_pages[0].bytes_len); ogm_make_video_header_pkt(&pkt, w, h, fps); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (uint64_t)0, (uint8_t)0x02, (uint8_t)0x00); buf_free(&pkt); ogm_make_video_comment_pkt(&pkt); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (uint64_t)0, (uint8_t)0x00, (uint8_t)0x00); buf_free(&pkt); for (ai=1; ai<=hdr_end && ai<audio_page_count; ai++){ buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len); } fps_d = (double)fps; half_frame = 0.5 / fps_d; for (ai=hdr_end+1; ai<audio_page_count; ai++){ double at = 0.0; if (sample_rate) at = (double)audio_pages[ai].granule / (double)sample_rate; while (v_i < frame_count){ double vt_next = (double)(v_i + 1) / fps_d; if (vt_next <= at + half_frame){ uint8_t eos = (uint8_t)((v_i + 1 == frame_count) ? 0x04 : 0x00); uint64_t gran = (uint64_t)(v_i + 1); uint8_t is_key = meta ? meta[v_i].key : 1; ogm_make_video_data_pkt(&pkt, frames[v_i].p, frames[v_i].len, is_key); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, gran, (uint8_t)0x00, eos); buf_free(&pkt); v_i++; } else break; } buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len); } } else { ogm_make_video_header_pkt(&pkt, w, h, fps); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (uint64_t)0, (uint8_t)0x02, (uint8_t)0x00); buf_free(&pkt); ogm_make_video_comment_pkt(&pkt); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (uint64_t)0, (uint8_t)0x00, (uint8_t)0x00); buf_free(&pkt); } while (v_i < frame_count){ uint8_t eos = (uint8_t)((v_i + 1 == frame_count) ? 0x04 : 0x00); uint64_t gran = (uint64_t)(v_i + 1); uint8_t is_key = meta ? meta[v_i].key : 1; ogm_make_video_data_pkt(&pkt, frames[v_i].p, frames[v_i].len, is_key); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, gran, (uint8_t)0x00, eos); buf_free(&pkt); v_i++; } fo = fopen(out_path, "wb"); if (!fo) die("Cannot open output file"); if (out.len && fwrite(out.p, 1, (size_t)out.len, fo) != (size_t)out.len){ fclose(fo); die("Write failed"); } fclose(fo); buf_free(&out); } /* CLI */ static int streq(const char* a, const char* B){ return strcmp(a,B)==0; } static void usage(const char* exe){ fprintf(stderr, "Usage:\n %s <input.dv> [output.ogv] [--fps N] [--validate]\n\n" "Options:\n" " --fps N set fps (default 25)\n" " --validate run ffmpeg decode test (optional; ffmpeg must be in PATH)\n" " -h,--help show help\n", exe); } static void make_default_out(const char* in, char* out, uint32_t outsz){ const char* dot = strrchr(in, '.'); uint32_t n; if (!dot){ snprintf(out, outsz, "%s.ogv", in); return; } n = (uint32_t)(dot - in); if (n + 5 >= outsz) die("Output path too long"); memcpy(out, in, (size_t)n); memcpy(out + n, ".ogv", 5); } int main(int argc, char** argv){ const char* in_path=0; const char* out_arg=0; char out_path[1024]; int fps=25; int validate=0; int i, positional=0; uint8_t* dv; uint32_t dv_len; uint32_t w,h,frames2,channels; uint8_t* video_es; uint32_t video_es_len; uint8_t* audio_ogg; uint32_t audio_ogg_len; FrameMeta* meta; Span* frames; uint32_t frame_count; OggPage* audio_pages=0; uint32_t audio_page_count=0; uint32_t sample_rate=0; if (argc < 2){ usage(argv[0]); return 2; } for (i=1;i<argc;i++){ const char* a = argv[i]; if (streq(a,"-h") || streq(a,"--help")){ usage(argv[0]); return 0; } if (streq(a,"--validate")){ validate=1; continue; } if (streq(a,"--fps")){ if (i+1>=argc){ fprintf(stderr,"Missing value for --fps\n\n"); usage(argv[0]); return 2; } fps = atoi(argv[++i]); if (fps<=0 || fps>240){ fprintf(stderr,"Invalid fps: %d\n", fps); return 2; } continue; } if (a[0]=='-'){ fprintf(stderr,"Unknown option: %s\n\n", a); usage(argv[0]); return 2; } if (positional==0){ in_path=a; positional++; continue; } if (positional==1){ out_arg=a; positional++; continue; } fprintf(stderr,"Too many arguments\n\n"); usage(argv[0]); return 2; } if (!in_path){ usage(argv[0]); return 2; } if (out_arg){ strncpy(out_path, out_arg, sizeof(out_path)-1); out_path[sizeof(out_path)-1]=0; } else { make_default_out(in_path, out_path, (uint32_t)sizeof(out_path)); } crc_table_init(); dv = read_file_all(in_path, &dv_len); if (!dv) die2("Cannot read input file: ", in_path); w=h=frames2=channels=0; video_es=0; video_es_len=0; audio_ogg=0; audio_ogg_len=0; meta=0; dv_extract(dv, dv_len, &w,&h, &frames2,&channels, &video_es,&video_es_len, &audio_ogg,&audio_ogg_len, &meta); frames = mpeg4_split_vop_frames(video_es, video_es_len, &frame_count); if (channels != 0){ audio_pages = ogg_parse_pages(audio_ogg, audio_ogg_len, &audio_page_count); if (!audio_pages || audio_page_count==0) die("Audio parse failed: no Ogg pages found"); sample_rate = vorbis_detect_sample_rate(audio_pages, audio_page_count); if (!sample_rate){ fprintf(stderr,"WARNING: could not detect Vorbis sample rate; assuming 44100.\n"); sample_rate = 44100; } } mux_ogv(out_path, audio_pages, audio_page_count, sample_rate, (int)w, (int)h, fps, frames, meta, frame_count, (channels != 0)); printf("ogv: %s\n", out_path); printf("video: MPEG-4 Part 2 (%ux%u, frames=%u, fps=%d)\n", w, h, frame_count, fps); if (channels != 0) printf("audio: Vorbis (sr=%u, pages=%u)\n", sample_rate, audio_page_count); else printf("audio: none\n"); if (validate){ char cmd[1400]; int rc; snprintf(cmd, sizeof(cmd), "ffmpeg -hide_banner -loglevel error -i \"%s\" -f null -", out_path); rc = system(cmd); if (rc != 0) fprintf(stderr,"WARNING: ffmpeg validation failed (ffmpeg missing or decode errors).\n"); } if (dv) free(dv); if (video_es) free(video_es); if (audio_ogg) free(audio_ogg); if (audio_pages) ogg_free_pages(audio_pages, audio_page_count); if (frames) free_frames(frames, frame_count); if (meta) free(meta); return 0; } Спасибо сказали:
|
| -=CHE@TER=- |
Mar 7 2026, 01:56
Сообщение
#12
|
|
Walter Sullivan ![]() ![]() ![]() Группа: Root Admin Сообщений: 1,428 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 327 раз(а) |
Мне кажется что у вас в муксинге проблема О-о-о! Вот это я фигни наделал! (рукалицо) Понял, огромное спасибо за подробности и детальный разбор! Я тогда ваш код оставлю. Есть надежда, что мы подошли к финалу разбора, так что выдаю версию со стандартными целочисленными типами, чтобы ничего не надо было менять. Если нет, то удалю пост и подумаем ещё. Да, всё отлично! Огромное вам спасибо за все исправления и улучшения! Теперь всё работает как часы.На моем тестовом сете все чисто. Проверил на всех файлах из "АЖ" и "ГЭГ2" через линуксовый MPlayer (проверял, правда, на быстрой перемотке, покадрово всё не отсматривал) - никаких проблем. Код, правда, не компилировался, потому что там ошибка была в объявлении строчных/заглавных переменных ("b" / "B"), но это я вручную поправил, пустяки. У меня два вопроса по программе: 1. Можно ли код под двумя лицензиями выложить? MIT и Apache 2? Ни в коем случае не настаиваю, просто Apache 2, насколько мне известно, более свободная - она также исключает ситуацию с патентами. 2. Можно ли в usage() какой-нибудь копирайт добавить? Потому что сейчас непонятно кто автор программы и куда обращаться, если не смотреть исходные коды. Это просто пожелания, можно всё оставить как есть. Если никаких ко мне вопросов больше не будет, то я тогда выкладываю программу на сайт вместо хака-плеера через "Angel.exe". Ещё есть просьба-предложение (если вам это интересно, конечно). Я смотрю вы очень хорошо с ИИ системами работаете. Есть несколько вещей, которые хотелось бы сделать и, мне кажется, ваш опыт с ИИ тут бы очень пригодился: I. На сайте есть программы с "позаимствованным" кодом конвертирования-распаковки (например). Т.е. программа на C, её я писал, но вот код распаковки был сложный, так что я его для экономии времени и сил просто выдрал из игры, скомпилировал и линковал отдельно. Я начал потихоньку такие программы переписывать, но в некоторых очень много ассемблерного кода и его сворачивать в код на сях - это муторно и долго. Было бы очень здорово такой код переделать на C без ассемблера, тогда это, во-первых, сделает программы портабельными под другие системы, компиляторы и процессоры (x64), а, во-вторых, код программ будет "чистым" с точки зрения лицензий. II. Если интересны именно эротические игры, то тут в комментариях и гостевой люди ещё несколько видео-конвертеров к эротическим играм выкладывали и там тоже всё как-то с кучей костылей и ffmpeg сделано. Можно их переписать нормально, как вы сделали с .DV форматом, и тоже добавить на сайт (по одной игре я даже могу помочь немного - пытался существующий конвертер разобрать и переписать). Вот такие идеи и предложения. Но, ещё раз повторюсь, если есть интерес/желание/время этим заниматься - никто никого не заставляет и расстраиваться не будет в случае отказа, у нас тут всё абсолютно добровольно и каждый занимается тем, чем ему нравится и хочется. Спасибо большое! |
| Lumis |
Mar 7 2026, 14:34
Сообщение
#13
|
|
Member ![]() ![]() Группа: Authorized Сообщений: 11 Регистрация: 2-March 26 Пользователь №: 18,038 Спасибо сказали: 11 раз(а) |
Да, всё отлично! Огромное вам спасибо за все исправления и улучшения! Теперь всё работает как часы. Проверил на всех файлах из "АЖ" и "ГЭГ2" через линуксовый MPlayer (проверял, правда, на быстрой перемотке, покадрово всё не отсматривал) - никаких проблем. Код, правда, не компилировался, потому что там ошибка была в объявлении строчных/заглавных переменных ("b" / "B"), но это я вручную поправил, пустяки. Это форумные смайлики, уже третий раз попадаю на них. Забыл отключить, потом отредактировал, отключил, а код уже испорчен. Тот случай если не ИИ галлюцинирует, так детерминированный форум вставляет проблемы в блоки кода :-) CODE /* dvbang2ogv.c * ANSI C / C89, Visual Studio 2008 friendly * License: MIT, Copyright 2026 Lumis * v1.5 uses stdint.h * * Converts proprietary "DV!!" .dv container into .ogv (OGM). * Video: MPEG-4 Part 2 (VOP start code 00 00 01 B6) kept byte-exact. * Audio: Ogg/Vorbis stream bytes kept byte-exact (no re-encode). * * Container format (as reverse engineered): * * dvf_head (0x4C bytes, little-endian u32 fields): * magic = "DV!!" (0x21215644 LE) * width = hdr[4] * height = hdr[5] * frames2 = hdr[7] (count of items/frames) * channels = hdr[8] (0 no audio, 2 stereo) * asize1st = hdr[9] (used bytes in preroll area; rest is zero padding) * * then: item table: frames2 * 0x14 bytes: * fsize: total packet bytes, with keyframe flag in bit31 * vsize: video bytes inside packet * zero1 * dsize: audio bytes inside packet (in many files dsize == total - vsize) * zero2 * * then: preroll audio region of 0xC000 bytes may exist (often padded with zeros); * only first asize1st bytes are meaningful audio bytes. * * then: frames2 packets of (fsize & 0x7fffffff) bytes. * packet layout: * video[vsize] + blob[blob_size] + audio[audio_size] * * where: * tail = total - vsize * audio_size= min(dsize, tail) <-- critical fix (53-99.dv needs this) * blob_size = tail - audio_size * * OGV/OGM mux: * - Copy Vorbis BOS page unchanged (byte-exact) * - Create OGM video BOS page (stream_header, subtype "XVID") * - Create OGM video comment page * - Copy remaining Vorbis header pages unchanged (until 3 Vorbis packets end) * - Interleave: * at = audio_page.granule / sample_rate * emit video frames while ((v_i+1)/fps) <= at + (0.5/fps) * then copy audio page * Finally flush remaining video frames. * * Determinism: * - video serial hardcoded to 0xC0DEC0DE (xor if equals audio serial). * * Build (small-ish EXE): * cl /O1 /Os /GL /GS- /Gy /GR- /EHsc- /MD dvbang2ogv.c /link /LTCG /OPT:REF /OPT:ICF /SUBSYSTEM:CONSOLE,5.01 */ #include <stdio.h> #include <stdlib.h> #include <string.h> /* ----- Standard integer types where available ----- */ #if defined(_MSC_VER) && (_MSC_VER < 1600) typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; typedef unsigned __int64 uint64_t; #else #include <stdint.h> #endif #ifdef _MSC_VER #ifndef snprintf #define snprintf _snprintf #endif #endif #define MAGIC_DV_LE 0x21215644UL /* "DV!!" LE */ #define PREROLL_BLOCK_SIZE 0xC000UL #define VIDEO_SERIAL_FIXED 0xC0DEC0DEUL static void die(const char* msg) { fprintf(stderr, "ERROR: %s\n", msg); exit(2); } static void die2(const char* msg, const char* more) { fprintf(stderr, "ERROR: %s%s\n", msg, more ? more : ""); exit(2); } /* LE helpers */ static uint32_t rd32le(const uint8_t* p){ return (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24); } static uint64_t rd64le(const uint8_t* p){ uint64_t v=0; v |= (uint64_t)p[0]; v |= (uint64_t)p[1]<<8; v |= (uint64_t)p[2]<<16; v |= (uint64_t)p[3]<<24; v |= (uint64_t)p[4]<<32; v |= (uint64_t)p[5]<<40; v |= (uint64_t)p[6]<<48; v |= (uint64_t)p[7]<<56; return v; } static void wr32le(uint8_t* p, uint32_t v){ p[0]=(uint8_t)v; p[1]=(uint8_t)(v>>8); p[2]=(uint8_t)(v>>16); p[3]=(uint8_t)(v>>24); } static void wr64le(uint8_t* p, uint64_t v){ p[0]=(uint8_t)v; p[1]=(uint8_t)(v>>8); p[2]=(uint8_t)(v>>16); p[3]=(uint8_t)(v>>24); p[4]=(uint8_t)(v>>32); p[5]=(uint8_t)(v>>40); p[6]=(uint8_t)(v>>48); p[7]=(uint8_t)(v>>56); } /* Buf */ typedef struct Buf { uint8_t* p; uint32_t len; uint32_t cap; } Buf; static void buf_init(Buf* b){ b->p=0; b->len=0; b->cap=0; } static void buf_reserve(Buf* b, uint32_t need){ uint32_t ncap; uint8_t* np; if (need <= b->cap) return; ncap = b->cap ? (b->cap + (b->cap>>1) + 64) : 256; if (ncap < need) ncap = need; np = (uint8_t*)realloc(b->p, (size_t)ncap); if (!np) die("Out of memory"); b->p=np; b->cap=ncap; } static void buf_append(Buf* b, const uint8_t* data, uint32_t n){ if (!n) return; buf_reserve(b, b->len + n); memcpy(b->p + b->len, data, (size_t)n); b->len += n; } static void buf_append_u8(Buf* b, uint8_t v){ buf_reserve(b, b->len + 1); b->p[b->len++] = v; } static void buf_free(Buf* b){ if (b->p) free(b->p); b->p=0; b->len=0; b->cap=0; } /* Read file */ static uint8_t* read_file_all(const char* path, uint32_t* out_len){ FILE* f; long sz; uint8_t* data; size_t got; f = fopen(path, "rb"); if (!f) return 0; fseek(f, 0, SEEK_END); sz = ftell(f); if (sz < 0) { fclose(f); return 0; } fseek(f, 0, SEEK_SET); data = (uint8_t*)malloc((size_t)sz); if (!data) { fclose(f); return 0; } got = (sz>0) ? fread(data, 1, (size_t)sz, f) : 0; fclose(f); if ((long)got != sz) { free(data); return 0; } *out_len = (uint32_t)sz; return data; } /* Ogg CRC */ static uint32_t CRC_TABLE[256]; static void crc_table_init(void){ uint32_t poly=0x04C11DB7UL, i, j, r; for (i=0;i<256;i++){ r = i<<24; for (j=0;j<8;j++){ if (r & 0x80000000UL) r = (r<<1) ^ poly; else r <<= 1; } CRC_TABLE[i]=r; } } static uint32_t ogg_crc(const uint8_t* page, uint32_t len){ uint32_t crc=0, i; for (i=0;i<len;i++){ uint8_t b=page[i]; crc = (crc<<8) ^ CRC_TABLE[((crc>>24) ^ b) & 0xFF]; } return crc; } /* Ogg page writer */ static void ogg_build_page_raw(Buf* out, const uint8_t* segs, uint32_t seg_count, const uint8_t* body, uint32_t body_len, uint32_t serial, uint32_t seq, uint64_t granule, uint8_t header_type) { uint8_t hdr[27]; Buf page; uint32_t crc; memset(hdr, 0, sizeof(hdr)); hdr[0]='O'; hdr[1]='g'; hdr[2]='g'; hdr[3]='S'; hdr[4]=0; hdr[5]=header_type; wr64le(hdr+6, granule); wr32le(hdr+14, serial); wr32le(hdr+18, seq); wr32le(hdr+22, 0); hdr[26]=(uint8_t)seg_count; buf_init(&page); buf_append(&page, hdr, 27); buf_append(&page, segs, seg_count); buf_append(&page, body, body_len); crc = ogg_crc(page.p, page.len); wr32le(page.p + 22, crc); buf_append(out, page.p, page.len); buf_free(&page); } /* Packet->pages splitting (fixes 255 segments limit) */ static void ogg_write_packet_split(Buf* out, const uint8_t* pkt, uint32_t pkt_len, uint32_t serial, uint32_t* io_seq, uint64_t granule_last, uint8_t header_type_first, uint8_t header_type_last) { uint32_t pos=0, seq=*io_seq; const uint32_t max_body = 255UL*255UL; while (pos < pkt_len){ uint32_t rem = pkt_len - pos; if (rem <= max_body){ uint8_t segs[255]; uint32_t seg_count=0; uint32_t n=rem; uint8_t ht; while (n >= 255){ segs[seg_count++] = 255; n -= 255; } segs[seg_count++] = (uint8_t)n; ht = (pos==0) ? (uint8_t)(header_type_first | header_type_last) : (uint8_t)(0x01 | header_type_last); ogg_build_page_raw(out, segs, seg_count, pkt + pos, rem, serial, seq, granule_last, ht); seq++; break; } else { uint8_t segs_full[255]; uint32_t i; uint8_t ht; for (i=0;i<255;i++) segs_full[i]=255; ht = (pos==0) ? header_type_first : 0x01; ogg_build_page_raw(out, segs_full, 255, pkt + pos, max_body, serial, seq, 0, ht); seq++; pos += max_body; } } *io_seq = seq; } /* Ogg page parse (audio only, for timestamps + raw copy) */ typedef struct OggPage { uint8_t header_type; uint64_t granule; uint32_t serial; uint32_t seq; uint8_t* segtable; uint32_t segtable_len; uint8_t* body; uint32_t body_len; uint8_t* bytes; uint32_t bytes_len; } OggPage; static OggPage* ogg_parse_pages(const uint8_t* data, uint32_t len, uint32_t* out_count){ OggPage* pages; uint32_t cap=64, count=0, i=0; pages = (OggPage*)calloc((size_t)cap, sizeof(OggPage)); if (!pages) die("Out of memory"); while (1){ uint32_t j,k,seg_off,body_off,body_len,end; int found; uint8_t nseg; found=0; for (j=i; j+4<=len; j++){ if (data[j]=='O' && data[j+1]=='g' && data[j+2]=='g' && data[j+3]=='S'){ found=1; break; } } if (!found) break; if (j+27 > len) break; nseg = data[j+26]; seg_off = j+27; body_off = seg_off + (uint32_t)nseg; if (body_off > len) break; body_len=0; for (k=0;k<(uint32_t)nseg;k++) body_len += data[seg_off+k]; end = body_off + body_len; if (end > len) break; if (count >= cap){ OggPage* np; cap = cap + cap/2 + 32; np = (OggPage*)realloc(pages, (size_t)cap*sizeof(OggPage)); if (!np) die("Out of memory"); pages=np; memset(pages+count, 0, (size_t)(cap-count)*sizeof(OggPage)); } pages[count].header_type = data[j+5]; pages[count].granule = rd64le(data + j + 6); pages[count].serial = rd32le(data + j + 14); pages[count].seq = rd32le(data + j + 18); pages[count].segtable_len = (uint32_t)nseg; pages[count].segtable = (uint8_t*)malloc((size_t)nseg); pages[count].body = (uint8_t*)malloc((size_t)body_len); pages[count].bytes_len = end - j; pages[count].bytes = (uint8_t*)malloc((size_t)pages[count].bytes_len); if (!pages[count].segtable || !pages[count].body || !pages[count].bytes) die("Out of memory"); memcpy(pages[count].segtable, data + seg_off, (size_t)nseg); memcpy(pages[count].body, data + body_off, (size_t)body_len); memcpy(pages[count].bytes, data + j, (size_t)pages[count].bytes_len); pages[count].body_len = body_len; count++; i = end; } *out_count = count; return pages; } static void ogg_free_pages(OggPage* pages, uint32_t count){ uint32_t i; if (!pages) return; for (i=0;i<count;i++){ if (pages[i].segtable) free(pages[i].segtable); if (pages[i].body) free(pages[i].body); if (pages[i].bytes) free(pages[i].bytes); } free(pages); } /* Vorbis sample rate detection */ static uint32_t vorbis_detect_sample_rate(const OggPage* pages, uint32_t count){ Buf cur; uint32_t i,sidx; buf_init(&cur); for (i=0;i<count;i++){ uint32_t off=0; for (sidx=0;sidx<pages[i].segtable_len;sidx++){ uint8_t s = pages[i].segtable[sidx]; buf_append(&cur, pages[i].body + off, (uint32_t)s); off += (uint32_t)s; if (s < 255){ if (cur.len >= 16 && cur.p[0]==0x01 && cur.p[1]=='v' && cur.p[2]=='o' && cur.p[3]=='r' && cur.p[4]=='b' && cur.p[5]=='i' && cur.p[6]=='s') { uint32_t sr = rd32le(cur.p + 12); buf_free(&cur); return sr; } cur.len = 0; } } } buf_free(&cur); return 0; } static uint32_t vorbis_header_end_page_index(const OggPage* pages, uint32_t count){ uint32_t pkt=0,i,s; for (i=0;i<count;i++){ for (s=0;s<pages[i].segtable_len;s++){ if (pages[i].segtable[s] < 255){ pkt++; if (pkt >= 3) return i; } } } return count ? (count-1) : 0; } /* DV extraction */ typedef struct FrameMeta { uint8_t key; } FrameMeta; static void dv_extract(const uint8_t* dv, uint32_t dv_len, uint32_t* out_w, uint32_t* out_h, uint32_t* out_frames, uint32_t* out_channels, uint8_t** out_video_es, uint32_t* out_video_es_len, uint8_t** out_audio_ogg, uint32_t* out_audio_ogg_len, FrameMeta** out_meta) { uint32_t w, h, frames2, channels, asize1st; uint32_t table_off, table_len, preroll_off; uint32_t i; uint64_t sum_total; uint32_t data_off_with_preroll, data_off_no_preroll, data_off; int has_preroll; Buf video, audio; FrameMeta* meta; uint32_t pos; if (dv_len < 0x4C) die("Input too small for DV!! header"); if (rd32le(dv + 0) != MAGIC_DV_LE) die("Bad DV!! signature"); w = rd32le(dv + 16); h = rd32le(dv + 20); frames2 = rd32le(dv + 28); channels = rd32le(dv + 32); asize1st = rd32le(dv + 36); table_off = 0x4C; table_len = frames2 * 0x14UL; preroll_off = table_off + table_len; if (dv_len < preroll_off) die("Truncated: frame table"); sum_total = 0; for (i=0;i<frames2;i++){ uint32_t base = table_off + i * 0x14UL; uint32_t fsize = rd32le(dv + base + 0); sum_total += (uint64_t)(fsize & 0x7FFFFFFFUL); } data_off_no_preroll = preroll_off; data_off_with_preroll = preroll_off + PREROLL_BLOCK_SIZE; if ((uint64_t)data_off_with_preroll + sum_total == (uint64_t)dv_len) has_preroll = 1; else if ((uint64_t)data_off_no_preroll + sum_total == (uint64_t)dv_len) has_preroll = 0; else { if (data_off_with_preroll < dv_len && (uint64_t)data_off_with_preroll + sum_total < (uint64_t)dv_len) { fprintf(stderr, "WARNING: size mismatch; assuming preroll block present.\n"); has_preroll = 1; } else if ((uint64_t)data_off_no_preroll + sum_total < (uint64_t)dv_len) { fprintf(stderr, "WARNING: size mismatch; assuming no preroll block.\n"); has_preroll = 0; } else { die("Cannot determine payload offset (size mismatch too large)"); } } data_off = has_preroll ? data_off_with_preroll : data_off_no_preroll; if (channels == 0) asize1st = 0; else { if (asize1st == 0xFFFFFFFFUL) asize1st = 0; if (asize1st > PREROLL_BLOCK_SIZE) { fprintf(stderr, "WARNING: asize1st > 0xC000; clamping.\n"); asize1st = PREROLL_BLOCK_SIZE; } } buf_init(&video); buf_init(&audio); if (channels != 0 && has_preroll && asize1st) { if (preroll_off + asize1st > dv_len) die("Truncated: preroll audio"); buf_append(&audio, dv + preroll_off, asize1st); } meta = (FrameMeta*)calloc((size_t)frames2, sizeof(FrameMeta)); if (!meta) die("Out of memory"); pos = data_off; for (i=0;i<frames2;i++){ uint32_t base = table_off + i * 0x14UL; uint32_t fsize = rd32le(dv + base + 0); uint32_t vsize = rd32le(dv + base + 4); uint32_t dsize = rd32le(dv + base + 12); uint32_t total = (fsize & 0x7FFFFFFFUL); const uint8_t* pkt = dv + pos; meta[i].key = (uint8_t)((fsize & 0x80000000UL) ? 1 : 0); if ((uint64_t)pos + (uint64_t)total > (uint64_t)dv_len) die("Truncated: frame data"); if (channels == 0) { if (total) buf_append(&video, pkt, total); pos += total; continue; } if (total < vsize) die("Corrupt item: total < vsize"); /* Strict packet layout: * video[vsize] + audio[min(dsize, total-vsize)] + padding */ if (vsize) buf_append(&video, pkt, vsize); if (total > vsize) { uint32_t tail = total - vsize; uint32_t audio_size = (dsize <= tail) ? dsize : tail; /* uint32_t padding_size = tail - audio_size; */ if (audio_size) { buf_append(&audio, pkt + vsize, audio_size); } /* padding is skipped */ } pos += total; } *out_w = w; *out_h = h; *out_frames = frames2; *out_channels = channels; *out_video_es = video.p; *out_video_es_len = video.len; *out_audio_ogg = audio.p; *out_audio_ogg_len = audio.len; *out_meta = meta; } /* MPEG-4 VOP split */ typedef struct Span { uint8_t* p; uint32_t len; } Span; static Span* mpeg4_split_vop_frames(const uint8_t* es, uint32_t es_len, uint32_t* out_count){ uint32_t* idxs; uint32_t cap=1024, count=0, i; Span* frames; uint32_t header_len; uint8_t* header=0; idxs = (uint32_t*)malloc((size_t)cap*sizeof(uint32_t)); if (!idxs) die("Out of memory"); i=0; while (i+4 <= es_len){ uint32_t j; int found=0; for (j=i; j+4<=es_len; j++){ if (es[j]==0x00 && es[j+1]==0x00 && es[j+2]==0x01 && es[j+3]==0xB6){ found=1; break; } } if (!found) break; if (count >= cap){ uint32_t* np; cap = cap + cap/2 + 256; np = (uint32_t*)realloc(idxs, (size_t)cap*sizeof(uint32_t)); if (!np) die("Out of memory"); idxs=np; } idxs[count++] = j; i = j + 4; } if (count==0){ free(idxs); die("No MPEG-4 VOP start codes found"); } header_len = idxs[0]; if (header_len){ header = (uint8_t*)malloc((size_t)header_len); if (!header) die("Out of memory"); memcpy(header, es, (size_t)header_len); } frames = (Span*)calloc((size_t)count, sizeof(Span)); if (!frames) die("Out of memory"); for (i=0;i<count;i++){ uint32_t start=idxs[i]; uint32_t end=(i+1<count)?idxs[i+1]:es_len; uint32_t flen=end-start; if (i==0 && header_len){ uint32_t total=header_len+flen; frames[i].p=(uint8_t*)malloc((size_t)total); if (!frames[i].p) die("Out of memory"); memcpy(frames[i].p, header, (size_t)header_len); memcpy(frames[i].p+header_len, es+start, (size_t)flen); frames[i].len=total; } else { frames[i].p=(uint8_t*)malloc((size_t)flen); if (!frames[i].p) die("Out of memory"); memcpy(frames[i].p, es+start, (size_t)flen); frames[i].len=flen; } } if (header) free(header); free(idxs); *out_count=count; return frames; } static void free_frames(Span* frames, uint32_t count){ uint32_t i; if (!frames) return; for (i=0;i<count;i++) if (frames[i].p) free(frames[i].p); free(frames); } /* OGM packet builders */ static void ogm_build_stream_header_video(uint8_t out52[52], int w, int h, int fps){ double tu_d; uint64_t time_unit; memset(out52, 0, 52); memcpy(out52 + 0, "video", 5); memcpy(out52 + 8, "XVID", 4); wr32le(out52 + 12, 52); tu_d = 10000000.0 / (double)fps; time_unit = (uint64_t)(tu_d + 0.5); wr64le(out52 + 16, time_unit); wr64le(out52 + 24, (uint64_t)1); wr32le(out52 + 32, 1); wr32le(out52 + 36, 0); wr32le(out52 + 44, (uint32_t)w); wr32le(out52 + 48, (uint32_t)h); } static void ogm_make_video_header_pkt(Buf* pkt, int w, int h, int fps){ uint8_t sh[52]; buf_init(pkt); ogm_build_stream_header_video(sh, w, h, fps); buf_append_u8(pkt, 0x01); buf_append(pkt, sh, 52); } static void ogm_make_video_comment_pkt(Buf* pkt){ const char* vendor="dvbang"; uint32_t vlen=(uint32_t)strlen(vendor); uint8_t tmp[4]; buf_init(pkt); buf_append_u8(pkt, 0x03); wr32le(tmp, vlen); buf_append(pkt, tmp, 4); buf_append(pkt, (const uint8_t*)vendor, vlen); wr32le(tmp, 0); buf_append(pkt, tmp, 4); } static void ogm_make_video_data_pkt(Buf* pkt, const uint8_t* frame, uint32_t frame_len, uint8_t is_key){ uint8_t flags = (uint8_t)(0x80 | (is_key ? 0x08 : 0x00)); buf_init(pkt); buf_append_u8(pkt, flags); buf_append_u8(pkt, 1); buf_append_u8(pkt, 0); buf_append(pkt, frame, frame_len); } /* mux */ static void mux_ogv(const char* out_path, const OggPage* audio_pages, uint32_t audio_page_count, uint32_t sample_rate, int w, int h, int fps, const Span* frames, const FrameMeta* meta, uint32_t frame_count, int has_audio) { FILE* fo; Buf out, pkt; uint32_t video_serial = VIDEO_SERIAL_FIXED; uint32_t vseq = 0; uint32_t v_i = 0; buf_init(&out); if (has_audio) { uint32_t audio_serial, hdr_end, ai; double fps_d, half_frame; if (audio_page_count == 0) die("No audio pages"); audio_serial = audio_pages[0].serial; if (video_serial == audio_serial) video_serial ^= 0xFFFFFFFFUL; hdr_end = vorbis_header_end_page_index(audio_pages, audio_page_count); buf_append(&out, audio_pages[0].bytes, audio_pages[0].bytes_len); ogm_make_video_header_pkt(&pkt, w, h, fps); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (uint64_t)0, (uint8_t)0x02, (uint8_t)0x00); buf_free(&pkt); ogm_make_video_comment_pkt(&pkt); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (uint64_t)0, (uint8_t)0x00, (uint8_t)0x00); buf_free(&pkt); for (ai=1; ai<=hdr_end && ai<audio_page_count; ai++){ buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len); } fps_d = (double)fps; half_frame = 0.5 / fps_d; for (ai=hdr_end+1; ai<audio_page_count; ai++){ double at = 0.0; if (sample_rate) at = (double)audio_pages[ai].granule / (double)sample_rate; while (v_i < frame_count){ double vt_next = (double)(v_i + 1) / fps_d; if (vt_next <= at + half_frame){ uint8_t eos = (uint8_t)((v_i + 1 == frame_count) ? 0x04 : 0x00); uint64_t gran = (uint64_t)(v_i + 1); uint8_t is_key = meta ? meta[v_i].key : 1; ogm_make_video_data_pkt(&pkt, frames[v_i].p, frames[v_i].len, is_key); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, gran, (uint8_t)0x00, eos); buf_free(&pkt); v_i++; } else break; } buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len); } } else { ogm_make_video_header_pkt(&pkt, w, h, fps); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (uint64_t)0, (uint8_t)0x02, (uint8_t)0x00); buf_free(&pkt); ogm_make_video_comment_pkt(&pkt); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, (uint64_t)0, (uint8_t)0x00, (uint8_t)0x00); buf_free(&pkt); } while (v_i < frame_count){ uint8_t eos = (uint8_t)((v_i + 1 == frame_count) ? 0x04 : 0x00); uint64_t gran = (uint64_t)(v_i + 1); uint8_t is_key = meta ? meta[v_i].key : 1; ogm_make_video_data_pkt(&pkt, frames[v_i].p, frames[v_i].len, is_key); ogg_write_packet_split(&out, pkt.p, pkt.len, video_serial, &vseq, gran, (uint8_t)0x00, eos); buf_free(&pkt); v_i++; } fo = fopen(out_path, "wb"); if (!fo) die("Cannot open output file"); if (out.len && fwrite(out.p, 1, (size_t)out.len, fo) != (size_t)out.len){ fclose(fo); die("Write failed"); } fclose(fo); buf_free(&out); } /* CLI */ static int streq(const char* a, const char* b){ return strcmp(a,b)==0; } static void usage(const char* exe){ fprintf(stderr, "Copyright 2026 Lumis, https://www.ctpax-x.org/ https://www.forum.ctpax-x.org/index.php?sho...26\n" "License: MIT\n\n" "Usage:\n %s <input.dv> [output.ogv] [--fps N] [--validate]\n\n" "Options:\n" " --fps N set fps (default 25)\n" " --validate run ffmpeg decode test (optional; ffmpeg must be in PATH)\n" " -h,--help show help\n", exe); } static void make_default_out(const char* in, char* out, uint32_t outsz){ const char* dot = strrchr(in, '.'); uint32_t n; if (!dot){ snprintf(out, outsz, "%s.ogv", in); return; } n = (uint32_t)(dot - in); if (n + 5 >= outsz) die("Output path too long"); memcpy(out, in, (size_t)n); memcpy(out + n, ".ogv", 5); } int main(int argc, char** argv){ const char* in_path=0; const char* out_arg=0; char out_path[1024]; int fps=25; int validate=0; int i, positional=0; uint8_t* dv; uint32_t dv_len; uint32_t w,h,frames2,channels; uint8_t* video_es; uint32_t video_es_len; uint8_t* audio_ogg; uint32_t audio_ogg_len; FrameMeta* meta; Span* frames; uint32_t frame_count; OggPage* audio_pages=0; uint32_t audio_page_count=0; uint32_t sample_rate=0; if (argc < 2){ usage(argv[0]); return 2; } for (i=1;i<argc;i++){ const char* a = argv[i]; if (streq(a,"-h") || streq(a,"--help")){ usage(argv[0]); return 0; } if (streq(a,"--validate")){ validate=1; continue; } if (streq(a,"--fps")){ if (i+1>=argc){ fprintf(stderr,"Missing value for --fps\n\n"); usage(argv[0]); return 2; } fps = atoi(argv[++i]); if (fps<=0 || fps>240){ fprintf(stderr,"Invalid fps: %d\n", fps); return 2; } continue; } if (a[0]=='-'){ fprintf(stderr,"Unknown option: %s\n\n", a); usage(argv[0]); return 2; } if (positional==0){ in_path=a; positional++; continue; } if (positional==1){ out_arg=a; positional++; continue; } fprintf(stderr,"Too many arguments\n\n"); usage(argv[0]); return 2; } if (!in_path){ usage(argv[0]); return 2; } if (out_arg){ strncpy(out_path, out_arg, sizeof(out_path)-1); out_path[sizeof(out_path)-1]=0; } else { make_default_out(in_path, out_path, (uint32_t)sizeof(out_path)); } crc_table_init(); dv = read_file_all(in_path, &dv_len); if (!dv) die2("Cannot read input file: ", in_path); w=h=frames2=channels=0; video_es=0; video_es_len=0; audio_ogg=0; audio_ogg_len=0; meta=0; dv_extract(dv, dv_len, &w,&h, &frames2,&channels, &video_es,&video_es_len, &audio_ogg,&audio_ogg_len, &meta); frames = mpeg4_split_vop_frames(video_es, video_es_len, &frame_count); if (channels != 0){ audio_pages = ogg_parse_pages(audio_ogg, audio_ogg_len, &audio_page_count); if (!audio_pages || audio_page_count==0) die("Audio parse failed: no Ogg pages found"); sample_rate = vorbis_detect_sample_rate(audio_pages, audio_page_count); if (!sample_rate){ fprintf(stderr,"WARNING: could not detect Vorbis sample rate; assuming 44100.\n"); sample_rate = 44100; } } mux_ogv(out_path, audio_pages, audio_page_count, sample_rate, (int)w, (int)h, fps, frames, meta, frame_count, (channels != 0)); printf("ogv: %s\n", out_path); printf("video: MPEG-4 Part 2 (%ux%u, frames=%u, fps=%d)\n", w, h, frame_count, fps); if (channels != 0) printf("audio: Vorbis (sr=%u, pages=%u)\n", sample_rate, audio_page_count); else printf("audio: none\n"); if (validate){ char cmd[1400]; int rc; snprintf(cmd, sizeof(cmd), "ffmpeg -hide_banner -loglevel error -i \"%s\" -f null -", out_path); rc = system(cmd); if (rc != 0) fprintf(stderr,"WARNING: ffmpeg validation failed (ffmpeg missing or decode errors).\n"); } if (dv) free(dv); if (video_es) free(video_es); if (audio_ogg) free(audio_ogg); if (audio_pages) ogg_free_pages(audio_pages, audio_page_count); if (frames) free_frames(frames, frame_count); if (meta) free(meta); return 0; } У меня два вопроса по программе: 1. Можно ли код под двумя лицензиями выложить? MIT и Apache 2? Ни в коем случае не настаиваю, просто Apache 2, насколько мне известно, более свободная - она также исключает ситуацию с патентами. Мы под патенты не попадем, хотелось бы максимально простой свободной лицензии. У нас тут примитивная сборка свободного контейнера перепаковкой байт, нет никакого ноу-хау с алгоритмами поиска путей и сжатия, даже мпег4 не трогаем, давайте оставим MIT. 2. Можно ли в usage() какой-нибудь копирайт добавить? Потому что сейчас непонятно кто автор программы и куда обращаться, если не смотреть исходные коды. Это просто пожелания, можно всё оставить как есть. Отличное предложение, готово. Если никаких ко мне вопросов больше не будет, то я тогда выкладываю программу на сайт вместо хака-плеера через "Angel.exe". Может отдельно добавим? Отличная же работа проведена по загрузке Angel.exe для референса. Я попробовал наклепать на коленке свой примитивный плеер, но не доволен пока результатом. Если вдруг будет вдохновение и устойчиво заработает и будет лучше, тогда и поменяем на него? Можно попросить выложить зип-архив с exe v1.5, исходным .c, лицензией, README.txt и двумя батниками (ниже) для конвертации всего в mp4 с таймкодами (они мне все же больше нравятся). CODE dvbang2ogv =========== Proprietary DV!! to OGV/MP4 converter Конвертер проприетарного формата DV!! в OGV/MP4 Copyright © 2026 Lumis https://www.ctpax-x.org/ Support forum topic / Тема поддержки: https://www.forum.ctpax-x.org/index.php?showtopic=326 License / Лицензия ------------------ MIT English ======= Overview -------- dvbang2ogv is a small command-line utility that converts a proprietary "DV!!" container used by some old games (ГЭГ 2, Ангел Желаний) into .ogv/.ogm files without re-encoding the media streams. The program preserves: - MPEG-4 Part 2 video bitstream byte-for-byte - Ogg/Vorbis audio data byte-for-byte This makes the conversion fast, deterministic, and suitable for archival or further remuxing. Typical workflow: 1. Convert proprietary .dv or disguised .avi file to .ogv 2. Remux .ogv to .mp4 with FFmpeg 3. Inject proper video timestamps into the copied MPEG-4 video stream using FFmpeg bitstream filter `setts` Important notes --------------- - The generated OGV/OGM file is the primary extraction result. - MP4 is produced later by FFmpeg as a remuxed delivery format. - No video or audio re-encoding is required in the normal workflow. - The source video stream has no proper timestamps, so remuxing directly to MP4 may produce broken seeking or playback timing unless timestamps are injected. - The provided .cmd scripts automate both steps: proprietary DV/AVI -> OGV -> MP4 Supported source format ----------------------- The tool was reverse-engineered for files with the "DV!!" signature. These files may have: - .dv extension - .avi extension, while actually not being standard AVI files The internal layout contains: - proprietary header - frame/item table - optional preroll audio region - interleaved packet data with MPEG-4 video and Ogg/Vorbis audio chunks Main features ------------- - ANSI C / C89 compatible source - Friendly to Microsoft Visual Studio 2008 - Single-file build - No external libraries required - Byte-exact preservation of embedded video and audio payloads - Optional FFmpeg validation mode - Suitable for batch conversion with the supplied Windows .cmd files Usage ----- dvbang2ogv <input.dv> [output.ogv] [--fps N] [--validate] Options ------- --fps N set fps (default 25) --validate run ffmpeg decode test (optional; ffmpeg must be in PATH) -h,--help show help Examples -------- dvbang2ogv intro.dv dvbang2ogv movie.avi output.ogv dvbang2ogv scene.dv scene.ogv --fps 25 dvbang2ogv clip.dv --validate How MP4 remuxing works ---------------------- The included batch scripts use FFmpeg to remux .ogv files into .mp4: - video is copied, not re-encoded - audio is copied when possible - FFmpeg bitstream filter `setts` generates correct timestamps for the raw MPEG-4 Part 2 stream - faststart is enabled so the MP4 is more convenient for players Typical FFmpeg command used by the scripts: ffmpeg -y -i "input.ogv" ^ -hide_banner ^ -map 0:v:0 -map 0:a? ^ -c:v copy ^ -bsf:v setts=time_base=1/25:pts=N:dts=N:duration=1 ^ -c:a copy ^ -movflags +faststart ^ "output.mp4" Requirements ------------ For dvbang2ogv: - Windows - no extra runtime dependencies in the normal case For optional validation and MP4 remux: - ffmpeg.exe available in PATH Build ----- Single-file build, no dependencies: cl /TC /O1 /Os /GS- /Gy /GL /MT dvbang2ogv.c ^ /link /LTCG /OPT:REF /OPT:ICF /INCREMENTAL:NO /RELEASE /SUBSYSTEM:CONSOLE Alternative small-ish build: cl /O1 /Os /GL /GS- /Gy /GR- /EHsc- /MD dvbang2ogv.c ^ /link /LTCG /OPT:REF /OPT:ICF /SUBSYSTEM:CONSOLE,5.01 Implementation notes -------------------- Current version: - v1.5 - uses stdint.h The converter: - reads the proprietary DV!! header - parses frame metadata table - extracts preroll audio bytes when present - rebuilds an OGV/OGM container - keeps Vorbis pages byte-exact where applicable - creates OGM video stream header using subtype "XVID" - interleaves video frames and audio pages deterministically Determinism ----------- For reproducibility, the video serial is hardcoded to 0xC0DEC0DE (with adjustment if it would collide with the audio serial). Batch scripts ------------- Two sample batch scripts are included: - convert_gag2.cmd For batch conversion of files from "ГЭГ2" - convert_angel.cmd For batch conversion of files from "Ангел желаний" Both scripts: - process all matching source files in the current directory - skip files that were already converted - call dvbang2ogv.exe first - then remux all .ogv files to .mp4 with FFmpeg and timestamp injection Recommended usage ----------------- 1. Put dvbang2ogv.exe into the directory with the game video files 2. Put ffmpeg.exe in PATH 3. Copy the appropriate .cmd script into the same directory 4. Run the script 5. Review generated .ogv and .mp4 files Troubleshooting --------------- 1. "Not a DV!! file" The input is likely not from the supported proprietary container format. 2. FFmpeg validation fails Ensure FFmpeg is installed and available in PATH. 3. MP4 has no sound in some players Some players have limited support for unusual copied audio layouts after remuxing. Test with VLC or MPC-HC first. 4. Wrong playback speed Use the correct FPS value. Default is 25. 5. File already exists The batch scripts intentionally skip files that were already processed. Disclaimer ---------- This tool is provided as-is, without warranty. Please keep original files until you confirm that conversion results are correct. Russian / Русский ================= Обзор ----- dvbang2ogv — это небольшая консольная утилита для конвертации проприетарного контейнера "DV!!", использовавшегося в некоторых старых играх (ГЭГ 2, Ангел Желаний), в .ogv/.ogm без перекодирования потоков. Программа сохраняет: - видеопоток MPEG-4 Part 2 побайтно без изменений - аудиоданные Ogg/Vorbis побайтно без изменений Благодаря этому конвертация выполняется быстро, детерминированно и подходит как для архивирования, так и для последующего ремакса. Типичный сценарий работы: 1. Конвертация проприетарного .dv или замаскированного .avi в .ogv 2. Ремакс .ogv в .mp4 через FFmpeg 3. Инъекция корректных таймкодов в копируемый поток MPEG-4 video с помощью bitstream filter `setts` Важные замечания ---------------- - Основной результат извлечения — это файл OGV/OGM. - MP4 создаётся на втором шаге через FFmpeg как удобный целевой контейнер. - В обычном режиме ни видео, ни аудио не перекодируются. - В исходном видеопотоке нет корректных временных меток, поэтому прямой ремакс в MP4 может приводить к проблемам с перемоткой, синхронизацией и воспроизведением, если не добавить таймкоды. - Прилагаемые .cmd-файлы автоматизируют оба шага: proprietary DV/AVI -> OGV -> MP4 Поддерживаемый исходный формат ------------------------------ Утилита рассчитана на файлы с сигнатурой "DV!!". Такие файлы могут иметь: - расширение .dv - расширение .avi, хотя фактически это не стандартный AVI Внутренне формат содержит: - проприетарный заголовок - таблицу кадров/элементов - опциональную область preroll audio - пакеты с MPEG-4 видео и Ogg/Vorbis аудио Основные возможности -------------------- - исходник на ANSI C / C89 - совместимость с Microsoft Visual Studio 2008 - сборка из одного файла - без внешних библиотек - побайтное сохранение вложенных видео- и аудиоданных - опциональная проверка через FFmpeg - удобная пакетная обработка через прилагаемые Windows .cmd-файлы Использование ------------- dvbang2ogv <input.dv> [output.ogv] [--fps N] [--validate] Опции ----- --fps N установить fps (по умолчанию 25) --validate выполнить проверку декодирования через ffmpeg (опционально; ffmpeg должен быть доступен в PATH) -h,--help показать справку Примеры ------- dvbang2ogv intro.dv dvbang2ogv movie.avi output.ogv dvbang2ogv scene.dv scene.ogv --fps 25 dvbang2ogv clip.dv --validate Как выполняется ремакс в MP4 ---------------------------- Прилагаемые batch-скрипты используют FFmpeg для ремакса .ogv в .mp4: - видео копируется без перекодирования - аудио по возможности тоже копируется - bitstream filter `setts` создаёт корректные таймкоды для сырого MPEG-4 Part 2 видеопотока - включён faststart для более удобного воспроизведения Типичная команда FFmpeg, используемая в скриптах: ffmpeg -y -i "input.ogv" ^ -hide_banner ^ -map 0:v:0 -map 0:a? ^ -c:v copy ^ -bsf:v setts=time_base=1/25:pts=N:dts=N:duration=1 ^ -c:a copy ^ -movflags +faststart ^ "output.mp4" Требования ---------- Для dvbang2ogv: - Windows - в обычном режиме дополнительные зависимости не нужны Для проверки и ремакса в MP4: - ffmpeg.exe должен быть доступен через PATH Сборка ------ Сборка из одного файла, без зависимостей: cl /TC /O1 /Os /GS- /Gy /GL /MT dvbang2ogv.c ^ /link /LTCG /OPT:REF /OPT:ICF /INCREMENTAL:NO /RELEASE /SUBSYSTEM:CONSOLE Альтернативный вариант компактной сборки: cl /O1 /Os /GL /GS- /Gy /GR- /EHsc- /MD dvbang2ogv.c ^ /link /LTCG /OPT:REF /OPT:ICF /SUBSYSTEM:CONSOLE,5.01 Примечания по реализации ------------------------ Текущая версия: - v1.5 - использует stdint.h Конвертер: - читает заголовок проприетарного формата DV!! - разбирает таблицу метаданных кадров - извлекает preroll-аудио при его наличии - заново собирает контейнер OGV/OGM - сохраняет Vorbis pages побайтно там, где это возможно - создаёт OGM video stream header с subtype "XVID" - детерминированно чередует видео- и аудиоданные Детерминированность ------------------- Для воспроизводимости video serial жёстко задан как 0xC0DEC0DE (с корректировкой, если он совпадёт с audio serial). Batch-скрипты ------------- В комплекте предполагаются два примера batch-файлов: - convert_gag2.cmd Для пакетной обработки файлов из "ГЭГ2" - convert_angel.cmd Для пакетной обработки файлов из "Ангел желаний" Оба скрипта: - обрабатывают все подходящие исходные файлы в текущей папке - пропускают уже сконвертированные файлы - сначала запускают dvbang2ogv.exe - затем ремаксят все .ogv в .mp4 через FFmpeg с добавлением таймкодов Рекомендуемый порядок использования ----------------------------------- 1. Поместите dvbang2ogv.exe в папку с игровыми видеофайлами 2. Убедитесь, что ffmpeg.exe доступен в PATH 3. Скопируйте нужный .cmd-файл в ту же папку 4. Запустите скрипт 5. Проверьте созданные .ogv и .mp4 Решение проблем --------------- 1. "Not a DV!! file" Скорее всего, исходный файл не относится к поддерживаемому проприетарному формату. 2. Ошибка проверки FFmpeg Убедитесь, что FFmpeg установлен и доступен в PATH. 3. В MP4 нет звука в некоторых плеерах Некоторые плееры ограниченно работают с необычными вариантами remux. Для начала проверьте файл в VLC или MPC-HC. 4. Неверная скорость воспроизведения Используйте правильное значение FPS. По умолчанию используется 25. 5. Файл уже существует Batch-скрипты специально пропускают уже обработанные файлы. Отказ от гарантий ----------------- Программа предоставляется "как есть", без каких-либо гарантий и отвественности. CODE @echo off REM convert_gag.cmd setlocal enabledelayedexpansion set FPS=25 echo ========================================== echo DV/AVI batch conversion started echo ========================================== REM ---- Convert all DV!! files ---- for %%F in (*.avi) do ( if not exist "%%~nF.ogv" ( echo [DV] Converting %%F ... dvbang2ogv.exe "%%F" if errorlevel 1 ( echo ERROR converting %%F ) ) else ( echo [DV] Skipping %%F (already converted) ) ) for %%F in (*.ogv) do ( if not exist "%%~nF.mp4" ( echo [DV] Converting %%F ... ffmpeg -y -i "%%F" ^ -hide_banner ^ -map 0:v:0 -map 0:a? ^ -c:v copy ^ -bsf:v setts=time_base=1/%FPS%:pts=N:dts=N:duration=1 ^ -c:a copy ^ -movflags +faststart ^ "%%~nF.mp4" if errorlevel 1 ( echo ERROR converting %%F ) ) else ( echo [MP4] Skipping %%F (already converted) ) ) echo ========================================== echo Done. echo ========================================== CODE @echo off REM convert_angel.cmd setlocal enabledelayedexpansion set FPS=25 echo ========================================== echo DV/AVI batch conversion started echo ========================================== REM ---- Convert all DV!! files ---- for %%F in (*.dv) do ( if not exist "%%~nF.ogv" ( echo [DV] Converting %%F ... dvbang2ogv.exe "%%F" if errorlevel 1 ( echo ERROR converting %%F ) ) else ( echo [DV] Skipping %%F (already converted) ) ) for %%F in (*.ogv) do ( if not exist "%%~nF.mp4" ( echo [DV] Converting %%F ... ffmpeg -y -i "%%F" ^ -hide_banner ^ -map 0:v:0 -map 0:a? ^ -c:v copy ^ -bsf:v setts=time_base=1/%FPS%:pts=N:dts=N:duration=1 ^ -c:a copy ^ -movflags +faststart ^ "%%~nF.mp4" if errorlevel 1 ( echo ERROR converting %%F ) ) else ( echo [MP4] Skipping %%F (already converted) ) ) echo ========================================== echo Done. echo ========================================== CODE Copyright 2026 Lumis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Ещё есть просьба-предложение (если вам это интересно, конечно). Я смотрю вы очень хорошо с ИИ системами работаете. Есть несколько вещей, которые хотелось бы сделать и, мне кажется, ваш опыт с ИИ тут бы очень пригодился: I. На сайте есть программы с "позаимствованным" кодом конвертирования-распаковки (например). Т.е. программа на C, её я писал, но вот код распаковки был сложный, так что я его для экономии времени и сил просто выдрал из игры, скомпилировал и линковал отдельно. Я начал потихоньку такие программы переписывать, но в некоторых очень много ассемблерного кода и его сворачивать в код на сях - это муторно и долго. Было бы очень здорово такой код переделать на C без ассемблера, тогда это, во-первых, сделает программы портабельными под другие системы, компиляторы и процессоры (x64), а, во-вторых, код программ будет "чистым" с точки зрения лицензий. Ага, я гляну. II. Если интересны именно эротические игры, то тут в комментариях и гостевой люди ещё несколько видео-конвертеров к эротическим играм выкладывали и там тоже всё как-то с кучей костылей и ffmpeg сделано. Можно их переписать нормально, как вы сделали с .DV форматом, и тоже добавить на сайт (по одной игре я даже могу помочь немного - пытался существующий конвертер разобрать и переписать). Вот такие идеи и предложения. Но, ещё раз повторюсь, если есть интерес/желание/время этим заниматься - никто никого не заставляет и расстраиваться не будет в случае отказа, у нас тут всё абсолютно добровольно и каждый занимается тем, чем ему нравится и хочется. Спасибо большое! Меня не столько эроквесты зацепили, сколько отдельно эти видео как техническая задача мне не давали покоя много лет - расширение .avi в ГЭГ 2 меня сбило с толку и я пытался разглядывать их очень давно и поднял руки вверх. А сейчас, сгрузив весь анализ файлов ИИ, получил такой ворох гипотез и позднее отсев негодных, что получилось написать первый рабочий прототип. Вот и вся моя тут мотивация. По другим играм вдохновения пока нет ) Спасибо сказали:
|
| -=CHE@TER=- |
Mar 7 2026, 17:51
Сообщение
#14
|
|
Walter Sullivan ![]() ![]() ![]() Группа: Root Admin Сообщений: 1,428 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 327 раз(а) |
Это форумные смайлики, уже третий раз попадаю на них. Забыл отключить, потом отредактировал, отключил, а код уже испорчен. Тот случай если не ИИ галлюцинирует, так детерминированный форум вставляет проблемы в блоки кода :-) Ахахаха! (*улыбается*)Засада, я посмотрел опции, думал, что эту штуку где-то в настройках профиля отключить можно, но нет. Видимо, я это видел в новых версиях форума где-то. Мы под патенты не попадем, хотелось бы максимально простой свободной лицензии. У нас тут примитивная сборка свободного контейнера перепаковкой байт, нет никакого ноу-хау с алгоритмами поиска путей и сжатия, даже мпег4 не трогаем, давайте оставим MIT. Понял, пусть так и будет. Мне просто пришлось немного текст на сайте менять. Там автоматически добавляется подвал к описанию программ про лицензию. Добавил туда окончание, что лицензия Apache 2, "если не указано обратное".Отличное предложение, готово. Спасибо!Может отдельно добавим? Отличная же работа проведена по загрузке Angel.exe для референса. Я попробовал наклепать на коленке свой примитивный плеер, но не доволен пока результатом. Если вдруг будет вдохновение и устойчиво заработает и будет лучше, тогда и поменяем на него? Ну, я, чтобы не мешался, переместил в "sources\_oldcode\".Можно попросить выложить зип-архив с exe v1.5, исходным .c, лицензией, README.txt и двумя батниками (ниже) для конвертации всего в mp4 с таймкодами (они мне все же больше нравятся). Сайт обновил. Какие изменения:1. Объединил оба .CMD в один "convert_video.cmd" - можно вместо for (*.dv) написать for (*.dv;*.avi). 2. Поправил readme, чтобы речь шла только об одном файле. 3. Текстовые файлы назвал "dvbagn2ogv.license.txt " и "dvbagn2ogv.readme.txt", чтобы было понятно к какой программе они относятся. Ага, я гляну. Спасибо большое! Повторюсь, только если это интересно и тогда лучше отдельную тему под это создать в этом подфоруме (понимаю, что программа уже существующая, но вопрос всё же про ресурсы).Меня не столько эроквесты зацепили, сколько отдельно эти видео как техническая задача мне не давали покоя много лет - расширение .avi в ГЭГ 2 меня сбило с толку и я пытался разглядывать их очень давно и поднял руки вверх. А сейчас, сгрузив весь анализ файлов ИИ, получил такой ворох гипотез и позднее отсев негодных, что получилось написать первый рабочий прототип. Вот и вся моя тут мотивация. По другим играм вдохновения пока нет ) А, всё понял. Просто у нас в гостевой часто люди которые эроквесты распаковывают в последнее время зависают, поэтому изначально так и подумал. И, да, ИИ, конечно, сейчас жуткие вещи творит. Я на примере вот этого видеоконвертера почувствовал насколько сильно мир изменился, а я (и, думаю, я не один такой) ещё толком не успел до конца это всё осознать. |
| Lumis |
Mar 9 2026, 15:52
Сообщение
#15
|
|
Member ![]() ![]() Группа: Authorized Сообщений: 11 Регистрация: 2-March 26 Пользователь №: 18,038 Спасибо сказали: 11 раз(а) |
Ну, я, чтобы не мешался, переместил в "sources\_oldcode\". Спасибо! Сайт обновил. Какие изменения: 1. Объединил оба .CMD в один "convert_video.cmd" - можно вместо for (*.dv) написать for (*.dv;*.avi). 2. Поправил readme, чтобы речь шла только об одном файле. 3. Текстовые файлы назвал "dvbagn2ogv.license.txt " и "dvbagn2ogv.readme.txt", чтобы было понятно к какой программе они относятся. Супер, спасибо за все! dvbagn можно исправить порядок букв baGN -> baNG (это воскл. знак в DV!! я так обозначил)? Спасибо большое! Повторюсь, только если это интересно и тогда лучше отдельную тему под это создать в этом подфоруме (понимаю, что программа уже существующая, но вопрос всё же про ресурсы). Готово, вот. Кажется ничего он из алгоритма не потерял, я в промте на это акцентировал. Даже откомментировал отличия от стандартного LZW. Спасибо сказали:
|
| -=CHE@TER=- |
Mar 9 2026, 18:18
Сообщение
#16
|
|
Walter Sullivan ![]() ![]() ![]() Группа: Root Admin Сообщений: 1,428 Регистрация: 4-February 08 Пользователь №: 3 Спасибо сказали: 327 раз(а) |
Супер, спасибо за все! dvbagn можно исправить порядок букв baGN -> baNG (это воскл. знак в DV!! я так обозначил)? Дичайше извиняюсь! Набирал вручную, вместо того чтобы имя файла скопировать, и опечатался. (рукалицо)Готово, вот. Кажется ничего он из алгоритма не потерял, я в промте на это акцентировал. Даже откомментировал отличия от стандартного LZW. Огромное спасибо! |
![]() ![]() |
| Упрощённая версия | Сейчас: 15th April 2026 - 12:40 |