mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 19:51:51 +09:00
TAV: experimental separate audio format mode
This commit is contained in:
@@ -29,7 +29,11 @@ const TAV_PACKET_IFRAME = 0x10
|
|||||||
const TAV_PACKET_PFRAME = 0x11
|
const TAV_PACKET_PFRAME = 0x11
|
||||||
const TAV_PACKET_GOP_UNIFIED = 0x12 // Unified 3D DWT GOP (temporal + spatial)
|
const TAV_PACKET_GOP_UNIFIED = 0x12 // Unified 3D DWT GOP (temporal + spatial)
|
||||||
const TAV_PACKET_AUDIO_MP2 = 0x20
|
const TAV_PACKET_AUDIO_MP2 = 0x20
|
||||||
|
const TAV_PACKET_AUDIO_NATIVE = 0x21
|
||||||
|
const TAV_PACKET_AUDIO_PCM_16LE = 0x22
|
||||||
|
const TAV_PACKET_AUDIO_ADPCM = 0x23
|
||||||
const TAV_PACKET_SUBTITLE = 0x30
|
const TAV_PACKET_SUBTITLE = 0x30
|
||||||
|
const TAV_PACKET_AUDIO_BUNDLED = 0x40 // Entire MP2 audio file in single packet
|
||||||
const TAV_PACKET_EXTENDED_HDR = 0xEF
|
const TAV_PACKET_EXTENDED_HDR = 0xEF
|
||||||
const TAV_PACKET_GOP_SYNC = 0xFC // GOP sync (N frames decoded from GOP block)
|
const TAV_PACKET_GOP_SYNC = 0xFC // GOP sync (N frames decoded from GOP block)
|
||||||
const TAV_PACKET_TIMECODE = 0xFD
|
const TAV_PACKET_TIMECODE = 0xFD
|
||||||
@@ -498,6 +502,12 @@ let asyncDecodePtr = 0 // Compressed data pointer to free after decode
|
|||||||
let asyncDecodeStartTime = 0 // When async decode started (for diagnostics)
|
let asyncDecodeStartTime = 0 // When async decode started (for diagnostics)
|
||||||
let shouldReadPackets = true // Gate packet reading: false when all 3 buffers are full
|
let shouldReadPackets = true // Gate packet reading: false when all 3 buffers are full
|
||||||
|
|
||||||
|
// Pre-decoded audio state (for bundled audio packet 0x40)
|
||||||
|
let predecodedPcmBuffer = null // Buffer holding pre-decoded PCM data
|
||||||
|
let predecodedPcmSize = 0 // Total size of pre-decoded PCM
|
||||||
|
let predecodedPcmOffset = 0 // Current position in PCM buffer for streaming
|
||||||
|
const PCM_UPLOAD_CHUNK = 2304 // Upload 1152 stereo samples per chunk (one MP2 frame worth)
|
||||||
|
|
||||||
let cueElements = []
|
let cueElements = []
|
||||||
let currentCueIndex = -1 // Track current cue position
|
let currentCueIndex = -1 // Track current cue position
|
||||||
let iframePositions = [] // Track I-frame positions for seeking: [{offset, frameNum}]
|
let iframePositions = [] // Track I-frame positions for seeking: [{offset, frameNum}]
|
||||||
@@ -526,6 +536,14 @@ function cleanupAsyncDecode() {
|
|||||||
}
|
}
|
||||||
decodingGopData = null
|
decodingGopData = null
|
||||||
|
|
||||||
|
// Free pre-decoded PCM buffer if present
|
||||||
|
if (predecodedPcmBuffer !== null) {
|
||||||
|
sys.free(predecodedPcmBuffer)
|
||||||
|
predecodedPcmBuffer = null
|
||||||
|
predecodedPcmSize = 0
|
||||||
|
predecodedPcmOffset = 0
|
||||||
|
}
|
||||||
|
|
||||||
// Reset GOP playback state
|
// Reset GOP playback state
|
||||||
currentGopSize = 0
|
currentGopSize = 0
|
||||||
currentGopFrameIndex = 0
|
currentGopFrameIndex = 0
|
||||||
@@ -1186,8 +1204,64 @@ try {
|
|||||||
shouldReadPackets = false
|
shouldReadPackets = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (packetType === TAV_PACKET_AUDIO_BUNDLED) {
|
||||||
|
// Bundled audio packet - entire MP2 file pre-decoded to PCM
|
||||||
|
// This removes MP2 decoding from the frame timing loop
|
||||||
|
let totalAudioSize = seqread.readInt()
|
||||||
|
|
||||||
|
if (!mp2Initialised) {
|
||||||
|
mp2Initialised = true
|
||||||
|
audio.mp2Init()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interactive) {
|
||||||
|
serial.println(`Pre-decoding ${(totalAudioSize / 1024).toFixed(1)} KB of MP2 audio...`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate temporary buffer for MP2 data
|
||||||
|
let mp2Buffer = sys.malloc(totalAudioSize)
|
||||||
|
seqread.readBytes(totalAudioSize, mp2Buffer)
|
||||||
|
|
||||||
|
// Estimate PCM size: MP2 ~10:1 compression ratio, so PCM ~10x larger
|
||||||
|
// Each MP2 frame decodes to 2304 bytes PCM (1152 stereo 16-bit samples)
|
||||||
|
// Allocate generous buffer (12x MP2 size to be safe)
|
||||||
|
const estimatedPcmSize = totalAudioSize * 12
|
||||||
|
predecodedPcmBuffer = sys.malloc(estimatedPcmSize)
|
||||||
|
predecodedPcmSize = 0
|
||||||
|
predecodedPcmOffset = 0
|
||||||
|
|
||||||
|
// Decode entire MP2 file to PCM
|
||||||
|
const MP2_DECODE_CHUNK = 2304 // ~2 MP2 frames at 192kbps
|
||||||
|
let srcOffset = 0
|
||||||
|
|
||||||
|
while (srcOffset < totalAudioSize) {
|
||||||
|
let remaining = totalAudioSize - srcOffset
|
||||||
|
let chunkSize = Math.min(MP2_DECODE_CHUNK, remaining)
|
||||||
|
|
||||||
|
// Copy MP2 chunk to audio peripheral decode buffer
|
||||||
|
sys.memcpy(mp2Buffer + srcOffset, SND_BASE_ADDR - 2368, chunkSize)
|
||||||
|
|
||||||
|
// Decode to PCM (goes to SND_BASE_ADDR)
|
||||||
|
audio.mp2Decode()
|
||||||
|
|
||||||
|
// Copy decoded PCM from peripheral to our storage buffer
|
||||||
|
// Each decode produces 2304 bytes of PCM
|
||||||
|
sys.memcpy(SND_BASE_ADDR, predecodedPcmBuffer + predecodedPcmSize, 2304)
|
||||||
|
predecodedPcmSize += 2304
|
||||||
|
|
||||||
|
srcOffset += chunkSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free MP2 buffer (no longer needed)
|
||||||
|
sys.free(mp2Buffer)
|
||||||
|
|
||||||
|
if (interactive) {
|
||||||
|
serial.println(`Pre-decoded ${(predecodedPcmSize / 1024).toFixed(1)} KB PCM (from ${(totalAudioSize / 1024).toFixed(1)} KB MP2)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
else if (packetType === TAV_PACKET_AUDIO_MP2) {
|
else if (packetType === TAV_PACKET_AUDIO_MP2) {
|
||||||
// MP2 Audio packet
|
// Legacy MP2 Audio packet (for backwards compatibility)
|
||||||
let audioLen = seqread.readInt()
|
let audioLen = seqread.readInt()
|
||||||
|
|
||||||
if (!mp2Initialised) {
|
if (!mp2Initialised) {
|
||||||
@@ -1200,6 +1274,16 @@ try {
|
|||||||
audio.mp2UploadDecoded(0)
|
audio.mp2UploadDecoded(0)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
else if (packetType === TAV_PACKET_AUDIO_NATIVE) {
|
||||||
|
// PCM length must not exceed 65536 bytes!
|
||||||
|
let zstdLen = seqread.readInt()
|
||||||
|
let zstdPtr = sys.malloc(zstdLen)
|
||||||
|
seqread.readBytes(zstdLen, zstdPtr)
|
||||||
|
let pcmLen = gzip.decompFromTo(zstdPtr, zstdLen, SND_BASE_ADDR - 65536)
|
||||||
|
if (pcmLen > 65536) throw Error(`PCM data too long -- got ${pcmLen} bytes`)
|
||||||
|
audio.setSampleUploadLength(0, pcmLen)
|
||||||
|
audio.startSampleUpload(0)
|
||||||
|
}
|
||||||
else if (packetType === TAV_PACKET_SUBTITLE) {
|
else if (packetType === TAV_PACKET_SUBTITLE) {
|
||||||
// Subtitle packet - same format as TEV
|
// Subtitle packet - same format as TEV
|
||||||
let packetSize = seqread.readInt()
|
let packetSize = seqread.readInt()
|
||||||
@@ -1359,6 +1443,21 @@ try {
|
|||||||
frameCount++
|
frameCount++
|
||||||
trueFrameCount++
|
trueFrameCount++
|
||||||
|
|
||||||
|
// Upload pre-decoded PCM audio if available (keeps audio queue fed)
|
||||||
|
if (predecodedPcmBuffer !== null && predecodedPcmOffset < predecodedPcmSize) {
|
||||||
|
let remaining = predecodedPcmSize - predecodedPcmOffset
|
||||||
|
let uploadSize = Math.min(PCM_UPLOAD_CHUNK, remaining)
|
||||||
|
|
||||||
|
// Copy PCM chunk to audio peripheral memory
|
||||||
|
sys.memcpy(predecodedPcmBuffer + predecodedPcmOffset, SND_BASE_ADDR, uploadSize)
|
||||||
|
|
||||||
|
// Set upload parameters and trigger upload to queue
|
||||||
|
audio.setSampleUploadLength(0, uploadSize)
|
||||||
|
audio.startSampleUpload(0)
|
||||||
|
|
||||||
|
predecodedPcmOffset += uploadSize
|
||||||
|
}
|
||||||
|
|
||||||
// Schedule next frame
|
// Schedule next frame
|
||||||
nextFrameTime += (frametime) // frametime is in nanoseconds from header
|
nextFrameTime += (frametime) // frametime is in nanoseconds from header
|
||||||
}
|
}
|
||||||
@@ -1456,6 +1555,11 @@ finally {
|
|||||||
sys.free(NEXT_FIELD_BUFFER)
|
sys.free(NEXT_FIELD_BUFFER)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Free pre-decoded PCM buffer if present
|
||||||
|
if (predecodedPcmBuffer !== null) {
|
||||||
|
sys.free(predecodedPcmBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
con.curs_set(1)
|
con.curs_set(1)
|
||||||
con.clear()
|
con.clear()
|
||||||
|
|
||||||
|
|||||||
@@ -960,9 +960,19 @@ transmission capability, and region-of-interest coding.
|
|||||||
0x11: P-frame (delta/skip frame)
|
0x11: P-frame (delta/skip frame)
|
||||||
0x12: GOP Unified (temporal 3D DWT with unified preprocessing)
|
0x12: GOP Unified (temporal 3D DWT with unified preprocessing)
|
||||||
0x1F: (prohibited)
|
0x1F: (prohibited)
|
||||||
|
<audio packets>
|
||||||
0x20: MP2 audio packet
|
0x20: MP2 audio packet
|
||||||
|
0x21: Zstd-compressed 8-bit PCM (32 KHz, audio hardware's native format)
|
||||||
|
0x22: Zstd-compressed 16-bit PCM (32 KHz, little endian)
|
||||||
|
0x23: Zstd-compressed ADPCM
|
||||||
|
<subtitles>
|
||||||
0x30: Subtitle in "Simple" format
|
0x30: Subtitle in "Simple" format
|
||||||
0x31: Subtitle in "Karaoke" format
|
0x31: Subtitle in "Karaoke" format
|
||||||
|
<synchronised tracks>
|
||||||
|
0x40: MP2 audio track
|
||||||
|
0x41: Zstd-compressed 8-bit PCM (32 KHz, audio hardware's native format)
|
||||||
|
0x42: Zstd-compressed 16-bit PCM (32 KHz, little endian)
|
||||||
|
0x43: Zstd-compressed ADPCM
|
||||||
<multiplexed video>
|
<multiplexed video>
|
||||||
0x70/71: Video channel 2 I/P-frame
|
0x70/71: Video channel 2 I/P-frame
|
||||||
0x72/73: Video channel 3 I/P-frame
|
0x72/73: Video channel 3 I/P-frame
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package net.torvald.tsvm
|
|||||||
import com.badlogic.gdx.utils.compression.Lzma
|
import com.badlogic.gdx.utils.compression.Lzma
|
||||||
import io.airlift.compress.zstd.ZstdInputStream
|
import io.airlift.compress.zstd.ZstdInputStream
|
||||||
import io.airlift.compress.zstd.ZstdOutputStream
|
import io.airlift.compress.zstd.ZstdOutputStream
|
||||||
|
import net.torvald.UnsafeHelper
|
||||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint
|
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
@@ -19,30 +20,51 @@ class CompressorDelegate(private val vm: VM) {
|
|||||||
*/
|
*/
|
||||||
fun compFromTo(input: Int, len: Int, output: Int): Int {
|
fun compFromTo(input: Int, len: Int, output: Int): Int {
|
||||||
val inbytes = ByteArray(len) { vm.peek(input.toLong() + it)!! }
|
val inbytes = ByteArray(len) { vm.peek(input.toLong() + it)!! }
|
||||||
comp(inbytes).let {
|
val bytes = comp(inbytes)
|
||||||
it.forEachIndexed { index, byte ->
|
vm.getDev(output.toLong(), bytes.size.toLong(), true).let {
|
||||||
vm.poke(output.toLong() + index, byte)
|
if (it != null) {
|
||||||
|
val bytesReversed = bytes.reversedArray() // copy over reversed bytes starting from the end of the destination
|
||||||
|
UnsafeHelper.memcpyRaw(bytesReversed, UnsafeHelper.getArrayOffset(bytesReversed), null, output.toLong() - bytes.size, bytes.size.toLong())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bytes.forEachIndexed { index, byte ->
|
||||||
|
vm.poke(output.toLong() + index, byte)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return it.size
|
|
||||||
}
|
}
|
||||||
|
return bytes.size
|
||||||
}
|
}
|
||||||
|
|
||||||
fun compTo(str: String, output: Int): Int {
|
fun compTo(str: String, output: Int): Int {
|
||||||
comp(str).let {
|
val bytes = comp(str)
|
||||||
it.forEachIndexed { index, byte ->
|
vm.getDev(output.toLong(), bytes.size.toLong(), true).let {
|
||||||
vm.poke(output.toLong() + index, byte)
|
if (it != null) {
|
||||||
|
val bytesReversed = bytes.reversedArray() // copy over reversed bytes starting from the end of the destination
|
||||||
|
UnsafeHelper.memcpyRaw(bytesReversed, UnsafeHelper.getArrayOffset(bytesReversed), null, output.toLong() - bytes.size, bytes.size.toLong())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bytes.forEachIndexed { index, byte ->
|
||||||
|
vm.poke(output.toLong() + index, byte)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return it.size
|
|
||||||
}
|
}
|
||||||
|
return bytes.size
|
||||||
}
|
}
|
||||||
|
|
||||||
fun compTo(ba: ByteArray, output: Int): Int {
|
fun compTo(ba: ByteArray, output: Int): Int {
|
||||||
comp(ba).let {
|
val bytes = comp(ba)
|
||||||
it.forEachIndexed { index, byte ->
|
vm.getDev(output.toLong(), bytes.size.toLong(), true).let {
|
||||||
vm.poke(output.toLong() + index, byte)
|
if (it != null) {
|
||||||
|
val bytesReversed = bytes.reversedArray() // copy over reversed bytes starting from the end of the destination
|
||||||
|
UnsafeHelper.memcpyRaw(bytesReversed, UnsafeHelper.getArrayOffset(bytesReversed), null, output.toLong() - bytes.size, bytes.size.toLong())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bytes.forEachIndexed { index, byte ->
|
||||||
|
vm.poke(output.toLong() + index, byte)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return it.size
|
|
||||||
}
|
}
|
||||||
|
return bytes.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -51,16 +73,32 @@ class CompressorDelegate(private val vm: VM) {
|
|||||||
|
|
||||||
fun decompTo(str: String, pointer: Int): Int {
|
fun decompTo(str: String, pointer: Int): Int {
|
||||||
val bytes = decomp(str)
|
val bytes = decomp(str)
|
||||||
bytes.forEachIndexed { index, byte ->
|
vm.getDev(pointer.toLong(), bytes.size.toLong(), true).let {
|
||||||
vm.poke(pointer.toLong() + index, byte)
|
if (it != null) {
|
||||||
|
val bytesReversed = bytes.reversedArray() // copy over reversed bytes starting from the end of the destination
|
||||||
|
UnsafeHelper.memcpyRaw(bytesReversed, UnsafeHelper.getArrayOffset(bytesReversed), null, pointer.toLong() - bytes.size, bytes.size.toLong())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bytes.forEachIndexed { index, byte ->
|
||||||
|
vm.poke(pointer.toLong() + index, byte)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return bytes.size
|
return bytes.size
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decompTo(ba: ByteArray, pointer: Int): Int {
|
fun decompTo(ba: ByteArray, pointer: Int): Int {
|
||||||
val bytes = decomp(ba)
|
val bytes = decomp(ba)
|
||||||
bytes.forEachIndexed { index, byte ->
|
vm.getDev(pointer.toLong(), bytes.size.toLong(), true).let {
|
||||||
vm.poke(pointer.toLong() + index, byte)
|
if (it != null) {
|
||||||
|
val bytesReversed = bytes.reversedArray() // copy over reversed bytes starting from the end of the destination
|
||||||
|
UnsafeHelper.memcpyRaw(bytesReversed, UnsafeHelper.getArrayOffset(bytesReversed), null, pointer.toLong() - bytes.size, bytes.size.toLong())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bytes.forEachIndexed { index, byte ->
|
||||||
|
vm.poke(pointer.toLong() + index, byte)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return bytes.size
|
return bytes.size
|
||||||
}
|
}
|
||||||
@@ -70,12 +108,19 @@ class CompressorDelegate(private val vm: VM) {
|
|||||||
*/
|
*/
|
||||||
fun decompFromTo(input: Int, len: Int, output: Int): Int {
|
fun decompFromTo(input: Int, len: Int, output: Int): Int {
|
||||||
val inbytes = ByteArray(len) { vm.peek(input.toLong() + it)!! }
|
val inbytes = ByteArray(len) { vm.peek(input.toLong() + it)!! }
|
||||||
decomp(inbytes).let {
|
val bytes = decomp(inbytes)
|
||||||
it.forEachIndexed { index, byte ->
|
vm.getDev(output.toLong(), bytes.size.toLong(), true).let {
|
||||||
vm.poke(output.toLong() + index, byte)
|
if (it != null) {
|
||||||
|
val bytesReversed = bytes.reversedArray() // copy over reversed bytes starting from the end of the destination
|
||||||
|
UnsafeHelper.memcpyRaw(bytesReversed, UnsafeHelper.getArrayOffset(bytesReversed), null, output.toLong() - bytes.size, bytes.size.toLong())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bytes.forEachIndexed { index, byte ->
|
||||||
|
vm.poke(output.toLong() + index, byte)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return it.size
|
|
||||||
}
|
}
|
||||||
|
return bytes.size
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -725,7 +725,7 @@ class VM(
|
|||||||
private fun relPtrInDev(from: Long, len: Long, start: Int, end: Int) =
|
private fun relPtrInDev(from: Long, len: Long, start: Int, end: Int) =
|
||||||
(from in start..end && (from + len) in start..end)
|
(from in start..end && (from + len) in start..end)
|
||||||
|
|
||||||
private fun getDev(from: Long, len: Long, isDest: Boolean): Long? {
|
internal fun getDev(from: Long, len: Long, isDest: Boolean): Long? {
|
||||||
return if (from >= 0) usermem.ptr + from
|
return if (from >= 0) usermem.ptr + from
|
||||||
// MMIO area
|
// MMIO area
|
||||||
else if (from in -1048576..-1 && (from - len) in -1048577..-1) {
|
else if (from in -1048576..-1 && (from - len) in -1048577..-1) {
|
||||||
@@ -745,6 +745,7 @@ class VM(
|
|||||||
else if (dev is AudioAdapter) {
|
else if (dev is AudioAdapter) {
|
||||||
if (relPtrInDev(fromRel, len, 64, 2367)) dev.mediaDecodedBin.ptr + fromRel - 64
|
if (relPtrInDev(fromRel, len, 64, 2367)) dev.mediaDecodedBin.ptr + fromRel - 64
|
||||||
else if (relPtrInDev(fromRel, len, 2368, 4096)) dev.mediaFrameBin.ptr + fromRel - 2368
|
else if (relPtrInDev(fromRel, len, 2368, 4096)) dev.mediaFrameBin.ptr + fromRel - 2368
|
||||||
|
else if (relPtrInDev(fromRel, len, 65536, 131072)) dev.pcmBin.ptr + fromRel - 65536
|
||||||
else null
|
else null
|
||||||
}
|
}
|
||||||
else if (dev is GraphicsAdapter) {
|
else if (dev is GraphicsAdapter) {
|
||||||
@@ -770,7 +771,9 @@ class VM(
|
|||||||
if (relPtrInDev(fromRel, len, 0, 250879)) dev.framebuffer.ptr + fromRel - 0
|
if (relPtrInDev(fromRel, len, 0, 250879)) dev.framebuffer.ptr + fromRel - 0
|
||||||
else if (relPtrInDev(fromRel, len, 250880, 251903)) dev.unusedArea.ptr + fromRel - 250880
|
else if (relPtrInDev(fromRel, len, 250880, 251903)) dev.unusedArea.ptr + fromRel - 250880
|
||||||
else if (relPtrInDev(fromRel, len, 253950, 261631)) dev.textArea.ptr + fromRel - 253950
|
else if (relPtrInDev(fromRel, len, 253950, 261631)) dev.textArea.ptr + fromRel - 253950
|
||||||
else if (relPtrInDev(fromRel, len, 262144, 513023)) dev.framebuffer2?.ptr?.plus(fromRel)?.minus(253950)
|
else if (relPtrInDev(fromRel, len, 262144, 513023)) dev.framebuffer2?.ptr?.plus(fromRel)?.minus(262144)
|
||||||
|
else if (relPtrInDev(fromRel, len, 524288, 775167)) dev.framebuffer3?.ptr?.plus(fromRel)?.minus(524288)
|
||||||
|
else if (relPtrInDev(fromRel, len, 786432, 1037371)) dev.framebuffer4?.ptr?.plus(fromRel)?.minus(786432)
|
||||||
else null
|
else null
|
||||||
}
|
}
|
||||||
else if (dev is RamBank) {
|
else if (dev is RamBank) {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
#define TAV_PACKET_BFRAME_ADAPTIVE 0x17 // B-frame with adaptive quad-tree block partitioning (bidirectional prediction)
|
#define TAV_PACKET_BFRAME_ADAPTIVE 0x17 // B-frame with adaptive quad-tree block partitioning (bidirectional prediction)
|
||||||
#define TAV_PACKET_AUDIO_MP2 0x20 // MP2 audio
|
#define TAV_PACKET_AUDIO_MP2 0x20 // MP2 audio
|
||||||
#define TAV_PACKET_SUBTITLE 0x30 // Subtitle packet
|
#define TAV_PACKET_SUBTITLE 0x30 // Subtitle packet
|
||||||
|
#define TAV_PACKET_AUDIO_TRACK 0x40 // Separate audio track (full MP2 file)
|
||||||
#define TAV_PACKET_EXTENDED_HDR 0xEF // Extended header packet
|
#define TAV_PACKET_EXTENDED_HDR 0xEF // Extended header packet
|
||||||
#define TAV_PACKET_GOP_SYNC 0xFC // GOP sync packet (N frames decoded)
|
#define TAV_PACKET_GOP_SYNC 0xFC // GOP sync packet (N frames decoded)
|
||||||
#define TAV_PACKET_TIMECODE 0xFD // Timecode packet
|
#define TAV_PACKET_TIMECODE 0xFD // Timecode packet
|
||||||
@@ -122,9 +123,9 @@ static int needs_alpha_channel(int channel_layout) {
|
|||||||
#define MOTION_THRESHOLD 24.0f // Flush if motion exceeds 24 pixels in any direction
|
#define MOTION_THRESHOLD 24.0f // Flush if motion exceeds 24 pixels in any direction
|
||||||
|
|
||||||
// Audio/subtitle constants (reused from TEV)
|
// Audio/subtitle constants (reused from TEV)
|
||||||
#define MP2_SAMPLE_RATE 32000
|
#define TSVM_AUDIO_SAMPLE_RATE 32000
|
||||||
#define MP2_DEFAULT_PACKET_SIZE 1152
|
#define MP2_DEFAULT_PACKET_SIZE 1152
|
||||||
#define PACKET_AUDIO_TIME ((double)MP2_DEFAULT_PACKET_SIZE / MP2_SAMPLE_RATE)
|
#define PACKET_AUDIO_TIME ((double)MP2_DEFAULT_PACKET_SIZE / TSVM_AUDIO_SAMPLE_RATE)
|
||||||
#define MAX_SUBTITLE_LENGTH 2048
|
#define MAX_SUBTITLE_LENGTH 2048
|
||||||
|
|
||||||
int debugDumpMade = 0;
|
int debugDumpMade = 0;
|
||||||
@@ -1742,6 +1743,7 @@ typedef struct tav_encoder_s {
|
|||||||
int grain_synthesis; // 1 = enable grain synthesis (default), 0 = disable
|
int grain_synthesis; // 1 = enable grain synthesis (default), 0 = disable
|
||||||
int use_delta_encoding;
|
int use_delta_encoding;
|
||||||
int delta_haar_levels; // Number of Haar DWT levels to apply to delta coefficients (0 = disabled)
|
int delta_haar_levels; // Number of Haar DWT levels to apply to delta coefficients (0 = disabled)
|
||||||
|
int separate_audio_track; // 1 = write entire MP2 file as packet 0x40 after header, 0 = interleave audio (default)
|
||||||
|
|
||||||
// Frame buffers - ping-pong implementation
|
// Frame buffers - ping-pong implementation
|
||||||
uint8_t *frame_rgb[2]; // [0] and [1] alternate between current and previous
|
uint8_t *frame_rgb[2]; // [0] and [1] alternate between current and previous
|
||||||
@@ -2253,6 +2255,7 @@ static void show_usage(const char *program_name) {
|
|||||||
printf(" -c, --channel-layout N Channel layout: 0=Y-Co-Cg, 1=Y-Co-Cg-A, 2=Y-only, 3=Y-A, 4=Co-Cg, 5=Co-Cg-A (default: 0)\n");
|
printf(" -c, --channel-layout N Channel layout: 0=Y-Co-Cg, 1=Y-Co-Cg-A, 2=Y-only, 3=Y-A, 4=Co-Cg, 5=Co-Cg-A (default: 0)\n");
|
||||||
printf(" -a, --arate N MP2 audio bitrate in kbps (overrides quality-based audio rate)\n");
|
printf(" -a, --arate N MP2 audio bitrate in kbps (overrides quality-based audio rate)\n");
|
||||||
printf(" Valid values: 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384\n");
|
printf(" Valid values: 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384\n");
|
||||||
|
printf(" --separate-audio-track Write entire MP2 file as single packet 0x40 (instead of interleaved)\n");
|
||||||
printf(" -S, --subtitles FILE SubRip (.srt) or SAMI (.smi) subtitle file\n");
|
printf(" -S, --subtitles FILE SubRip (.srt) or SAMI (.smi) subtitle file\n");
|
||||||
printf(" --fontrom-lo FILE Low font ROM file for internationalised subtitles\n");
|
printf(" --fontrom-lo FILE Low font ROM file for internationalised subtitles\n");
|
||||||
printf(" --fontrom-hi FILE High font ROM file for internationalised subtitles\n");
|
printf(" --fontrom-hi FILE High font ROM file for internationalised subtitles\n");
|
||||||
@@ -2340,6 +2343,7 @@ static tav_encoder_t* create_encoder(void) {
|
|||||||
enc->grain_synthesis = 0; // Default: disable grain synthesis (only do it on the decoder)
|
enc->grain_synthesis = 0; // Default: disable grain synthesis (only do it on the decoder)
|
||||||
enc->use_delta_encoding = 0;
|
enc->use_delta_encoding = 0;
|
||||||
enc->delta_haar_levels = TEMPORAL_DECOMP_LEVEL;
|
enc->delta_haar_levels = TEMPORAL_DECOMP_LEVEL;
|
||||||
|
enc->separate_audio_track = 0; // Default: interleave audio packets
|
||||||
|
|
||||||
// GOP / temporal DWT settings
|
// GOP / temporal DWT settings
|
||||||
enc->enable_temporal_dwt = 1; // Mutually exclusive with use_delta_encoding
|
enc->enable_temporal_dwt = 1; // Mutually exclusive with use_delta_encoding
|
||||||
@@ -8595,8 +8599,65 @@ static long write_extended_header(tav_encoder_t *enc) {
|
|||||||
return endt_offset + 4 + 1; // 4 bytes for "ENDT", 1 byte for type
|
return endt_offset + 4 + 1; // 4 bytes for "ENDT", 1 byte for type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write separate audio track packet (0x40) - entire MP2 file in one packet
|
||||||
|
static int write_separate_audio_track(tav_encoder_t *enc, FILE *output) {
|
||||||
|
if (!enc->has_audio || !enc->mp2_file) {
|
||||||
|
return 0; // No audio to write
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file size
|
||||||
|
fseek(enc->mp2_file, 0, SEEK_END);
|
||||||
|
size_t mp2_size = ftell(enc->mp2_file);
|
||||||
|
fseek(enc->mp2_file, 0, SEEK_SET);
|
||||||
|
|
||||||
|
if (mp2_size == 0) {
|
||||||
|
fprintf(stderr, "Warning: MP2 file is empty\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate buffer for entire MP2 file
|
||||||
|
uint8_t *mp2_buffer = malloc(mp2_size);
|
||||||
|
if (!mp2_buffer) {
|
||||||
|
fprintf(stderr, "Error: Failed to allocate buffer for separate audio track (%zu bytes)\n", mp2_size);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read entire MP2 file
|
||||||
|
size_t bytes_read = fread(mp2_buffer, 1, mp2_size, enc->mp2_file);
|
||||||
|
if (bytes_read != mp2_size) {
|
||||||
|
fprintf(stderr, "Error: Failed to read MP2 file (expected %zu bytes, got %zu)\n", mp2_size, bytes_read);
|
||||||
|
free(mp2_buffer);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write packet type 0x40
|
||||||
|
uint8_t packet_type = TAV_PACKET_AUDIO_TRACK;
|
||||||
|
fwrite(&packet_type, 1, 1, output);
|
||||||
|
|
||||||
|
// Write payload size (uint32)
|
||||||
|
uint32_t payload_size = (uint32_t)mp2_size;
|
||||||
|
fwrite(&payload_size, sizeof(uint32_t), 1, output);
|
||||||
|
|
||||||
|
// Write MP2 data
|
||||||
|
fwrite(mp2_buffer, 1, mp2_size, output);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
free(mp2_buffer);
|
||||||
|
|
||||||
|
if (enc->verbose) {
|
||||||
|
printf("Separate audio track written: %zu bytes (packet 0x40)\n", mp2_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Process audio for current frame (copied and adapted from TEV)
|
// Process audio for current frame (copied and adapted from TEV)
|
||||||
static int process_audio(tav_encoder_t *enc, int frame_num, FILE *output) {
|
static int process_audio(tav_encoder_t *enc, int frame_num, FILE *output) {
|
||||||
|
// Skip if separate audio track mode is enabled
|
||||||
|
if (enc->separate_audio_track) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (!enc->has_audio || !enc->mp2_file || enc->audio_remaining <= 0) {
|
if (!enc->has_audio || !enc->mp2_file || enc->audio_remaining <= 0) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -8698,6 +8759,11 @@ static int process_audio(tav_encoder_t *enc, int frame_num, FILE *output) {
|
|||||||
// Process audio for a GOP (multiple frames at once)
|
// Process audio for a GOP (multiple frames at once)
|
||||||
// Accumulates deficit for N frames and emits all necessary audio packets
|
// Accumulates deficit for N frames and emits all necessary audio packets
|
||||||
static int process_audio_for_gop(tav_encoder_t *enc, int *frame_numbers, int num_frames, FILE *output) {
|
static int process_audio_for_gop(tav_encoder_t *enc, int *frame_numbers, int num_frames, FILE *output) {
|
||||||
|
// Skip if separate audio track mode is enabled
|
||||||
|
if (enc->separate_audio_track) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (!enc->has_audio || !enc->mp2_file || enc->audio_remaining <= 0 || num_frames == 0) {
|
if (!enc->has_audio || !enc->mp2_file || enc->audio_remaining <= 0 || num_frames == 0) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -9081,6 +9147,7 @@ int main(int argc, char *argv[]) {
|
|||||||
{"bframes", required_argument, 0, 1023},
|
{"bframes", required_argument, 0, 1023},
|
||||||
{"gop-size", required_argument, 0, 1024},
|
{"gop-size", required_argument, 0, 1024},
|
||||||
{"ezbc", no_argument, 0, 1025},
|
{"ezbc", no_argument, 0, 1025},
|
||||||
|
{"separate-audio-track", no_argument, 0, 1026},
|
||||||
{"help", no_argument, 0, '?'},
|
{"help", no_argument, 0, '?'},
|
||||||
{0, 0, 0, 0}
|
{0, 0, 0, 0}
|
||||||
};
|
};
|
||||||
@@ -9290,6 +9357,10 @@ int main(int argc, char *argv[]) {
|
|||||||
enc->enable_ezbc = 1;
|
enc->enable_ezbc = 1;
|
||||||
printf("EZBC (Embedded Zero Block Coding) enabled for significance maps\n");
|
printf("EZBC (Embedded Zero Block Coding) enabled for significance maps\n");
|
||||||
break;
|
break;
|
||||||
|
case 1026: // --separate-audio-track
|
||||||
|
enc->separate_audio_track = 1;
|
||||||
|
printf("Separate audio track mode enabled (packet 0x40)\n");
|
||||||
|
break;
|
||||||
case 'a':
|
case 'a':
|
||||||
int bitrate = atoi(optarg);
|
int bitrate = atoi(optarg);
|
||||||
int valid_bitrate = validate_mp2_bitrate(bitrate);
|
int valid_bitrate = validate_mp2_bitrate(bitrate);
|
||||||
@@ -9461,6 +9532,11 @@ int main(int argc, char *argv[]) {
|
|||||||
gettimeofday(&enc->start_time, NULL);
|
gettimeofday(&enc->start_time, NULL);
|
||||||
enc->extended_header_offset = write_extended_header(enc);
|
enc->extended_header_offset = write_extended_header(enc);
|
||||||
|
|
||||||
|
// Write separate audio track if enabled (packet 0x40)
|
||||||
|
if (enc->separate_audio_track) {
|
||||||
|
write_separate_audio_track(enc, enc->output_fp);
|
||||||
|
}
|
||||||
|
|
||||||
// Write font ROM packets if provided
|
// Write font ROM packets if provided
|
||||||
if (enc->fontrom_lo_file) {
|
if (enc->fontrom_lo_file) {
|
||||||
if (write_fontrom_packet(enc->output_fp, enc->fontrom_lo_file, 0x80) != 0) {
|
if (write_fontrom_packet(enc->output_fp, enc->fontrom_lo_file, 0x80) != 0) {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#define TAV_PACKET_AUDIO_MP2 0x20
|
#define TAV_PACKET_AUDIO_MP2 0x20
|
||||||
#define TAV_PACKET_SUBTITLE 0x30
|
#define TAV_PACKET_SUBTITLE 0x30
|
||||||
#define TAV_PACKET_SUBTITLE_KAR 0x31
|
#define TAV_PACKET_SUBTITLE_KAR 0x31
|
||||||
|
#define TAV_PACKET_AUDIO_TRACK 0x40
|
||||||
#define TAV_PACKET_VIDEO_CH2_I 0x70
|
#define TAV_PACKET_VIDEO_CH2_I 0x70
|
||||||
#define TAV_PACKET_VIDEO_CH2_P 0x71
|
#define TAV_PACKET_VIDEO_CH2_P 0x71
|
||||||
#define TAV_PACKET_VIDEO_CH3_I 0x72
|
#define TAV_PACKET_VIDEO_CH3_I 0x72
|
||||||
@@ -108,6 +109,7 @@ const char* get_packet_type_name(uint8_t type) {
|
|||||||
case TAV_PACKET_AUDIO_MP2: return "AUDIO MP2";
|
case TAV_PACKET_AUDIO_MP2: return "AUDIO MP2";
|
||||||
case TAV_PACKET_SUBTITLE: return "SUBTITLE (Simple)";
|
case TAV_PACKET_SUBTITLE: return "SUBTITLE (Simple)";
|
||||||
case TAV_PACKET_SUBTITLE_KAR: return "SUBTITLE (Karaoke)";
|
case TAV_PACKET_SUBTITLE_KAR: return "SUBTITLE (Karaoke)";
|
||||||
|
case TAV_PACKET_AUDIO_TRACK: return "AUDIO TRACK (Separate MP2)";
|
||||||
case TAV_PACKET_EXIF: return "METADATA (EXIF)";
|
case TAV_PACKET_EXIF: return "METADATA (EXIF)";
|
||||||
case TAV_PACKET_ID3V1: return "METADATA (ID3v1)";
|
case TAV_PACKET_ID3V1: return "METADATA (ID3v1)";
|
||||||
case TAV_PACKET_ID3V2: return "METADATA (ID3v2)";
|
case TAV_PACKET_ID3V2: return "METADATA (ID3v2)";
|
||||||
@@ -629,6 +631,19 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case TAV_PACKET_AUDIO_TRACK: {
|
||||||
|
stats.audio_count++;
|
||||||
|
uint32_t size;
|
||||||
|
if (fread(&size, sizeof(uint32_t), 1, fp) != 1) break;
|
||||||
|
stats.total_audio_bytes += size;
|
||||||
|
|
||||||
|
if (!opts.summary_only && display) {
|
||||||
|
printf(" - size=%u bytes (separate track)", size);
|
||||||
|
}
|
||||||
|
fseek(fp, size, SEEK_CUR);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case TAV_PACKET_SUBTITLE:
|
case TAV_PACKET_SUBTITLE:
|
||||||
case TAV_PACKET_SUBTITLE_KAR: {
|
case TAV_PACKET_SUBTITLE_KAR: {
|
||||||
stats.subtitle_count++;
|
stats.subtitle_count++;
|
||||||
|
|||||||
Reference in New Issue
Block a user