code wiki / _hdl_build / nx_mp4_mux.nx

nx_mp4_mux.nx source

↩ module page · 111 lines · 7612 B

1// nx_mp4_mux.nx -- SOVEREIGN ISOBMFF (MP4) MUXER. Closes the ffmpeg "remux to MP4 for final output" gap with the 2// Nishi stack: writes a standard, faststart (moov-before-mdat) .mp4 from an elementary video stream + its codec 3// config -- the container a downloader needs so HLS/DASH segments (and later a video+audio pair) play everywhere. 4// We already had the DEMUX side (nx_m4b_book: be32/find_atom atom walker); this is the MUX side it lacked. 5// An MP4 is a tree of boxes [size:u32be][type:4][payload]; sizes are BACKPATCHED after nested content is written 6// (robust vs hand-computed sizes). Single video track here; audio track (mp4a/esds) + interleave = the next rung. 7// license_tier: ORIGINAL 8import "nx_syscalls.nx" 9 10func mm_w8(b: *u8, o: i64, v: i64) -> i64 { b[o] = (v & 0xff) as u8; return o + 1 } 11func mm_w16(b: *u8, o: i64, v: i64) -> i64 { b[o] = ((v>>8)&0xff) as u8; b[o+1] = (v&0xff) as u8; return o + 2 } 12func mm_w32(b: *u8, o: i64, v: i64) -> i64 { b[o] = ((v>>24)&0xff) as u8; b[o+1] = ((v>>16)&0xff) as u8; b[o+2] = ((v>>8)&0xff) as u8; b[o+3] = (v&0xff) as u8; return o + 4 } 13func mm_tag(b: *u8, o: i64, t: *u8) -> i64 { b[o]=t[0]; b[o+1]=t[1]; b[o+2]=t[2]; b[o+3]=t[3]; return o + 4 } 14func mm_bytes(b: *u8, o: i64, src: *u8, n: i64) -> i64 { var i: i64=0; while i<n { b[o+i]=src[i]; i=i+1 } return o + n } 15func mm_zeros(b: *u8, o: i64, n: i64) -> i64 { var i: i64=0; while i<n { b[o+i]=0 as u8; i=i+1 } return o + n } 16func mm_patch(b: *u8, at: i64, endo: i64) -> i64 { mm_w32(b, at, endo - at); return 0 } // box size = end-start 17func mm_matrix(b: *u8, o: i64) -> i64 { // ISO unity matrix 18 o = mm_w32(b,o,0x00010000); o = mm_w32(b,o,0); o = mm_w32(b,o,0) 19 o = mm_w32(b,o,0); o = mm_w32(b,o,0x00010000); o = mm_w32(b,o,0) 20 o = mm_w32(b,o,0); o = mm_w32(b,o,0); o = mm_w32(b,o,0x40000000) 21 return o 22} 23 24// MUX a single H.264 video track -> faststart .mp4 in `out`. Returns total bytes. 25// w,h = pixel dims; timescale = ticks/sec; dur = ticks per sample (uniform); avcc/avcc_len = the avcC config 26// (SPS/PPS); sampdata = concatenated sample bytes; sizes[i] = byte size of sample i; nsamps = sample count. 27func mp4_mux_video(out: *u8, w: i64, h: i64, timescale: i64, dur: i64, avcc: *u8, avcc_len: i64, sampdata: *u8, sizes: *i64, nsamps: i64) -> i64 { 28 var total_data: i64 = 0 29 var si: i64 = 0 30 while si < nsamps { total_data = total_data + sizes[si]; si = si + 1 } 31 let total_dur: i64 = nsamps * dur 32 var o: i64 = 0 33 34 // ftyp 35 var s: i64 = o; o = o + 4; o = mm_tag(out, o, "ftyp" as *u8) 36 o = mm_tag(out, o, "isom" as *u8); o = mm_w32(out, o, 0x200) 37 o = mm_tag(out, o, "isom" as *u8); o = mm_tag(out, o, "iso2" as *u8); o = mm_tag(out, o, "avc1" as *u8); o = mm_tag(out, o, "mp41" as *u8) 38 mm_patch(out, s, o) 39 40 // moov 41 let moov_s: i64 = o; o = o + 4; o = mm_tag(out, o, "moov" as *u8) 42 // mvhd 43 s = o; o = o + 4; o = mm_tag(out, o, "mvhd" as *u8) 44 o = mm_w32(out,o,0); o = mm_w32(out,o,0); o = mm_w32(out,o,0) // ver/flags, cre, mod 45 o = mm_w32(out,o,timescale); o = mm_w32(out,o,total_dur) 46 o = mm_w32(out,o,0x00010000); o = mm_w16(out,o,0x0100); o = mm_w16(out,o,0); o = mm_zeros(out,o,8) // rate, vol, res 47 o = mm_matrix(out,o); o = mm_zeros(out,o,24); o = mm_w32(out,o,2) // matrix, pre_defined, next_track_id 48 mm_patch(out, s, o) 49 // trak 50 let trak_s: i64 = o; o = o + 4; o = mm_tag(out, o, "trak" as *u8) 51 // tkhd (flags 0x07 = enabled|inmovie|inpreview) 52 s = o; o = o + 4; o = mm_tag(out, o, "tkhd" as *u8) 53 o = mm_w32(out,o,0x00000007); o = mm_w32(out,o,0); o = mm_w32(out,o,0) // ver/flags, cre, mod 54 o = mm_w32(out,o,1); o = mm_w32(out,o,0); o = mm_w32(out,o,total_dur) // track_id, res, duration 55 o = mm_zeros(out,o,8); o = mm_w16(out,o,0); o = mm_w16(out,o,0); o = mm_w16(out,o,0); o = mm_w16(out,o,0) // res, layer, alt, vol, res 56 o = mm_matrix(out,o); o = mm_w32(out,o,w<<16); o = mm_w32(out,o,h<<16) 57 mm_patch(out, s, o) 58 // mdia 59 let mdia_s: i64 = o; o = o + 4; o = mm_tag(out, o, "mdia" as *u8) 60 // mdhd 61 s = o; o = o + 4; o = mm_tag(out, o, "mdhd" as *u8) 62 o = mm_w32(out,o,0); o = mm_w32(out,o,0); o = mm_w32(out,o,0) 63 o = mm_w32(out,o,timescale); o = mm_w32(out,o,total_dur); o = mm_w16(out,o,0x55c4); o = mm_w16(out,o,0) // lang=und 64 mm_patch(out, s, o) 65 // hdlr 66 s = o; o = o + 4; o = mm_tag(out, o, "hdlr" as *u8) 67 o = mm_w32(out,o,0); o = mm_w32(out,o,0); o = mm_tag(out,o,"vide" as *u8); o = mm_zeros(out,o,12) 68 o = mm_bytes(out,o,"VideoHandler" as *u8,12); o = mm_w8(out,o,0) 69 mm_patch(out, s, o) 70 // minf 71 let minf_s: i64 = o; o = o + 4; o = mm_tag(out, o, "minf" as *u8) 72 // vmhd 73 s = o; o = o + 4; o = mm_tag(out, o, "vmhd" as *u8); o = mm_w32(out,o,1); o = mm_w16(out,o,0); o = mm_zeros(out,o,6); mm_patch(out, s, o) 74 // dinf/dref/url (self-contained) 75 s = o; o = o + 4; o = mm_tag(out, o, "dinf" as *u8) 76 var s2: i64 = o; o = o + 4; o = mm_tag(out, o, "dref" as *u8); o = mm_w32(out,o,0); o = mm_w32(out,o,1) 77 var s3: i64 = o; o = o + 4; o = mm_tag(out, o, "url " as *u8); o = mm_w32(out,o,1); mm_patch(out, s3, o) 78 mm_patch(out, s2, o); mm_patch(out, s, o) 79 // stbl 80 let stbl_s: i64 = o; o = o + 4; o = mm_tag(out, o, "stbl" as *u8) 81 // stsd -> avc1 -> avcC 82 s = o; o = o + 4; o = mm_tag(out, o, "stsd" as *u8); o = mm_w32(out,o,0); o = mm_w32(out,o,1) 83 var av: i64 = o; o = o + 4; o = mm_tag(out, o, "avc1" as *u8) 84 o = mm_zeros(out,o,6); o = mm_w16(out,o,1); o = mm_w16(out,o,0); o = mm_w16(out,o,0); o = mm_zeros(out,o,12) // res, data_ref_idx, predef, res, predef 85 o = mm_w16(out,o,w); o = mm_w16(out,o,h); o = mm_w32(out,o,0x00480000); o = mm_w32(out,o,0x00480000) // w,h,hres,vres 86 o = mm_w32(out,o,0); o = mm_w16(out,o,1); o = mm_zeros(out,o,32); o = mm_w16(out,o,0x0018); o = mm_w16(out,o,0xffff) // res, framecount, compressorname, depth, predef 87 var ac: i64 = o; o = o + 4; o = mm_tag(out, o, "avcC" as *u8); o = mm_bytes(out,o,avcc,avcc_len); mm_patch(out, ac, o) 88 mm_patch(out, av, o); mm_patch(out, s, o) 89 // stts (1 run: nsamps @ dur) 90 s = o; o = o + 4; o = mm_tag(out, o, "stts" as *u8); o = mm_w32(out,o,0); o = mm_w32(out,o,1); o = mm_w32(out,o,nsamps); o = mm_w32(out,o,dur); mm_patch(out, s, o) 91 // stsc (1 chunk holds all samples) 92 s = o; o = o + 4; o = mm_tag(out, o, "stsc" as *u8); o = mm_w32(out,o,0); o = mm_w32(out,o,1); o = mm_w32(out,o,1); o = mm_w32(out,o,nsamps); o = mm_w32(out,o,1); mm_patch(out, s, o) 93 // stsz (per-sample sizes) 94 s = o; o = o + 4; o = mm_tag(out, o, "stsz" as *u8); o = mm_w32(out,o,0); o = mm_w32(out,o,0); o = mm_w32(out,o,nsamps) 95 si = 0; while si < nsamps { o = mm_w32(out,o,sizes[si]); si = si + 1 } 96 mm_patch(out, s, o) 97 // stco (1 chunk offset -- BACKPATCHED to the mdat data start once known) 98 s = o; o = o + 4; o = mm_tag(out, o, "stco" as *u8); o = mm_w32(out,o,0); o = mm_w32(out,o,1) 99 let stco_at: i64 = o; o = mm_w32(out,o,0) 100 mm_patch(out, s, o) 101 mm_patch(out, stbl_s, o); mm_patch(out, minf_s, o); mm_patch(out, mdia_s, o); mm_patch(out, trak_s, o); mm_patch(out, moov_s, o) 102 103 // mdat 104 let mdat_s: i64 = o; o = o + 4; o = mm_tag(out, o, "mdat" as *u8) 105 let mdat_data: i64 = o 106 o = mm_bytes(out, o, sampdata, total_data) 107 mm_patch(out, mdat_s, o) 108 mm_w32(out, stco_at, mdat_data) // chunk offset = absolute file offset of sample data 109 110 return o 111}