Jump to content
cubanismo

Bug in JagCinePak tool?

Recommended Posts

@JagMod, I think I've found a bug in the latest version of your cinepak tool.  A good portion of the movies I encode have the audio glitch then cut out after some time.  At first I thought this was just because my bitrates were too high, but it happens even when the movie is well under the required bitrate.  Debugging in the cpkdemo source, I noticed audio samples are being fed to the DSP before it can even pick up & start processing the prior sample, leading some to start getting dropped.  I looked at the output of the flminfo tool from the Atari tools, and normally at the settings I was using, there's an audio sample every 7-8 frames of video samples.  However, near the end of the movie, I start seeing an audio sample every frame!  I went and inspected the JagCinePak log files, and eventually for some reason the logic seemingly breaks down and indeed starts interleaving Audio samples in between every frame. Excerpt:

 

MovieToFilm() #13a: Video chunks = 545, Audio chunks = 74
MovieToFilm() #13b: s = 5399, ChunkCount = 6018
### Reading video data, m = 16892554, sampledata = 16892398, mediatime = 194709
MovieToFilm() #14a: GetMediaSample() = 0
MovieToFilm() #14b: videosize = 6500, sampleTime = 194709, durationPerSample = 41
MovieToFilm() #15: SoundMedia[0] = 17269026, SamplesRemaining = 484450
MovieToFilm() #20a: Adjusting Table->Samples[] and incrmenting 's'
MovieToFilm() #20b: videosize = 6500, dataOffset = 34778016, s = 5400
	(w) video data (size 6500) (fileoffset 0x02142408): 1019641400ffffffb4
MovieToFilm() #21: At bottom of loop,  s = 5400
MovieToFilm() #13a: Video chunks = 544, Audio chunks = 74
MovieToFilm() #13b: s = 5400, ChunkCount = 6018
### Reading video data, m = 16892554, sampledata = 16892398, mediatime = 194750
MovieToFilm() #14a: GetMediaSample() = 0
MovieToFilm() #14b: videosize = 6496, sampleTime = 194750, durationPerSample = 41
MovieToFilm() #15: SoundMedia[0] = 17269026, SamplesRemaining = 484450
MovieToFilm() #20a: Adjusting Table->Samples[] and incrmenting 's'
MovieToFilm() #20b: videosize = 6496, dataOffset = 34784516, s = 5401
	(w) video data (size 6496) (fileoffset 0x02143d6c): 1019601400ffffffb4
MovieToFilm() #21: At bottom of loop,  s = 5401
MovieToFilm() #13a: Video chunks = 543, Audio chunks = 74
MovieToFilm() #13b: s = 5401, ChunkCount = 6018
### Reading video data, m = 16892554, sampledata = 16892398, mediatime = 194791
MovieToFilm() #14a: GetMediaSample() = 0
MovieToFilm() #14b: videosize = 6488, sampleTime = 194791, durationPerSample = 41
MovieToFilm() #15: SoundMedia[0] = 17269026, SamplesRemaining = 484450
MovieToFilm() #16a: GetMediaSample() = 0
MovieToFilm() #16b: audiosize = 6616, sampleTime = 4300400, num_samples_read = 6616
MovieToFilm() #18: Writing sound chunk at 4300400, 6616 samples (6616 bytes)
MovieToFilm() #19a: num_samples_read (audio) = 6616, dataOffset = 34791012
MovieToFilm() #19b: incrementing chunk counter, s = 5402
	(w) sound data (size 6616) (fileoffset 0x021456cc): 00000000
MovieToFilm() #20a: Adjusting Table->Samples[] and incrmenting 's'
MovieToFilm() #20b: videosize = 6488, dataOffset = 34797628, s = 5403
	(w) video data (size 6488) (fileoffset 0x021470a4): 1019581400ffffffb4
MovieToFilm() #21: At bottom of loop,  s = 5403
MovieToFilm() #13a: Video chunks = 542, Audio chunks = 73
MovieToFilm() #13b: s = 5403, ChunkCount = 6018
### Reading video data, m = 16892554, sampledata = 16892398, mediatime = 194832
MovieToFilm() #14a: GetMediaSample() = 0
MovieToFilm() #14b: videosize = 6492, sampleTime = 194832, durationPerSample = 41
MovieToFilm() #15: SoundMedia[0] = 17269026, SamplesRemaining = 477834
MovieToFilm() #16a: GetMediaSample() = 0
MovieToFilm() #16b: audiosize = 6616, sampleTime = 4307016, num_samples_read = 6616
MovieToFilm() #18: Writing sound chunk at 4307016, 6616 samples (6616 bytes)
MovieToFilm() #19a: num_samples_read (audio) = 6616, dataOffset = 34804116
MovieToFilm() #19b: incrementing chunk counter, s = 5404
	(w) sound data (size 6616) (fileoffset 0x021489fc): fefefdfefffefeff
MovieToFilm() #20a: Adjusting Table->Samples[] and incrmenting 's'
MovieToFilm() #20b: videosize = 6492, dataOffset = 34810732, s = 5405
	(w) video data (size 6492) (fileoffset 0x0214a3d4): 10195c1400ffffffb4
MovieToFilm() #21: At bottom of loop,  s = 5405
MovieToFilm() #13a: Video chunks = 541, Audio chunks = 72
MovieToFilm() #13b: s = 5405, ChunkCount = 6018
### Reading video data, m = 16892554, sampledata = 16892398, mediatime = 194873
MovieToFilm() #14a: GetMediaSample() = 0
MovieToFilm() #14b: videosize = 6492, sampleTime = 194873, durationPerSample = 41
MovieToFilm() #15: SoundMedia[0] = 17269026, SamplesRemaining = 471218
MovieToFilm() #16a: GetMediaSample() = 0
MovieToFilm() #16b: audiosize = 6616, sampleTime = 4313632, num_samples_read = 6616
MovieToFilm() #18: Writing sound chunk at 4313632, 6616 samples (6616 bytes)
MovieToFilm() #19a: num_samples_read (audio) = 6616, dataOffset = 34817224
MovieToFilm() #19b: incrementing chunk counter, s = 5406
	(w) sound data (size 6616) (fileoffset 0x0214bd30): 420fcf9fafdff
MovieToFilm() #20a: Adjusting Table->Samples[] and incrmenting 's'
MovieToFilm() #20b: videosize = 6492, dataOffset = 34823840, s = 5407
	(w) video data (size 6492) (fileoffset 0x0214d708): 10195c1400ffffffb4
MovieToFilm() #21: At bottom of loop,  s = 5407
MovieToFilm() #13a: Video chunks = 540, Audio chunks = 71
MovieToFilm() #13b: s = 5407, ChunkCount = 6018
### Reading video data, m = 16892554, sampledata = 16892398, mediatime = 194914
MovieToFilm() #14a: GetMediaSample() = 0
MovieToFilm() #14b: videosize = 6496, sampleTime = 194914, durationPerSample = 41
MovieToFilm() #15: SoundMedia[0] = 17269026, SamplesRemaining = 464602
MovieToFilm() #16a: GetMediaSample() = 0
MovieToFilm() #16b: audiosize = 6616, sampleTime = 4320248, num_samples_read = 6616
MovieToFilm() #18: Writing sound chunk at 4320248, 6616 samples (6616 bytes)
MovieToFilm() #19a: num_samples_read (audio) = 6616, dataOffset = 34830332
MovieToFilm() #19b: incrementing chunk counter, s = 5408
	(w) sound data (size 6616) (fileoffset 0x0214f064): 00000035
MovieToFilm() #20a: Adjusting Table->Samples[] and incrmenting 's'
MovieToFilm() #20b: videosize = 6496, dataOffset = 34836948, s = 5409
	(w) video data (size 6496) (fileoffset 0x02150a3c): 1019601400ffffffb4
MovieToFilm() #21: At bottom of loop,  s = 5409
MovieToFilm() #13a: Video chunks = 539, Audio chunks = 70
MovieToFilm() #13b: s = 5409, ChunkCount = 6018
### Reading video data, m = 16892554, sampledata = 16892398, mediatime = 194955
MovieToFilm() #14a: GetMediaSample() = 0
MovieToFilm() #14b: videosize = 6492, sampleTime = 194955, durationPerSample = 41
MovieToFilm() #15: SoundMedia[0] = 17269026, SamplesRemaining = 457986
MovieToFilm() #16a: GetMediaSample() = 0
MovieToFilm() #16b: audiosize = 6616, sampleTime = 4326864, num_samples_read = 6616
MovieToFilm() #18: Writing sound chunk at 4326864, 6616 samples (6616 bytes)
MovieToFilm() #19a: num_samples_read (audio) = 6616, dataOffset = 34843444
MovieToFilm() #19b: incrementing chunk counter, s = 5410
	(w) sound data (size 6616) (fileoffset 0x0215239c): 00022357
MovieToFilm() #20a: Adjusting Table->Samples[] and incrmenting 's'
MovieToFilm() #20b: videosize = 6492, dataOffset = 34850060, s = 5411
	(w) video data (size 6492) (fileoffset 0x02153d74): 10195c1400ffffffb4
MovieToFilm() #21: At bottom of loop,  s = 5411

 

With that understanding of the issue, if I listen very closely, I can indeed hear the DSP thread trying to cram all the remaining sound from the clip into the next few frames at this point (The "glitch"), then, presumably because of this accelerated rate of inserting samples, it quickly runs out of audio chunks and stops writing them entirely, leaving the rest of the movie to play in silence.  I haven't gone and examined all the logs, but it seems to happen consistently around this time in the clips, whether I select different chunk durations (makes sense, given it seems to go wrong before converting to chunky video) or different audio interleave rates (Used 30/100ths of a second here).  Seems like some calculation based on the time is overflowing or something around this point.

 

Attaching the full MovieToFilm.log and output of flminfo -v2 for reference.

 

BTW, if you're not still maintaining JagCinePak, or even if you are, would you mind sharing the sources?  I'm doing this under WINE on Linux (Tried native windows 10 64-bit too, same behavior), but it would be great to be able to create a cross-platform toolchain or even just debug this myself.  I don't care if it's ugly.  Most source code is ;-)

ct-1.txt MovieToFilm.log

  • Like 4

Share this post


Link to post
Share on other sites

This is great news. Been a few years since I encoded a movie but iirc, @ovalbugmann posted having to 'slice' 5 minute movie segments into RAW chunks then chain them together for the final movie.

Here, I think. Anyway, this would be epic to have fixed. Great find :)

  • Like 1

Share this post


Link to post
Share on other sites
Posted (edited)

Yeah, I've been thinking I could write a little python script or something to repack the broken movies and verify it results in playable, correctly synced movies.  Wouldn't need to completely reverse engineer the algorithm to pick how far apart they should be; just look at the pattern from the 'good' part of the file and carry it forward.

 

The thread @Saturn references actually made me think further about the implications of this bug.  It isn't limited to the audio cutting out; that's just the most obvious outcome.  Audio samples are pretty big.  If 8 frames worth of them are interleaved every frame, that's going to drive up the total bitrate a lot.  I thought back and many of my files were previously locking up the Jaguar at around the same point the audio cut out in my latest tests.  The difference?  I lowered the audio sample size (lowered the audio sample length in JagCinePak) from 75/100ths of a second to 30/100ths of a second.  That dramatically lowered the worst-case bitrate of my films in flminfo output and stopped the lockups, leaving me with working video but the audio glitches and silence.  I didn't think too hard about why.  I figured I was just getting unlucky before and an audio sample landed right next to a chunk full of keyframes or something.

 

Now I suspect the real difference is that smaller audio samples interleaved every frame affect local bitrate less than larger audio samples interleaved every frame: I'd fixed the lockup by ensuring the data-rate bubble introduced by slamming the remainder of the video's audio samples into a handful of frames was still small enough it didn't exhaust cpkdemo's 1MB bbufferand/or the JagCD's read speed.

 

In simpler terms, this bug could also explain movie lockups and visual anomalies at around the same 5 minute mark, not just audio drop outs.  Would be great to have this fixed. Anyone know how to summon @JagMod?
 

I also have a theory that using a smaller timescale might work around this bug somewhat.  That's not a good idea for videos in general, but probably doesn't matter given there's no seeking or anything in all the Jag Cinepak uses I'm aware of.  Changing the timescale is kinda hard though.  I don't know if you can even do it in quicktime, or if you have to manually parse & edit the file with a script or something.

 

 

Edited by cubanismo
Expanded on fixer script idea, fixed weird formatting errors from mobile input
  • Like 1

Share this post


Link to post
Share on other sites

Slight concept change for you though - it's not the sound getting out of sync with the video, it's the video getting out of sync with the sound. 

 

The sound frame is generated first to cover the upcoming number of video blobs (be that I or P frames). Somehow the conversion routines have decided that after about 194 seconds, there is only one frame of video required to fill the time space the sound occupies. Sound blobs do not have a time associated with them (that's why they have 0xffffffff attached to the time tag in the blob header).

 

Looking at the disassembly shows a bit of signed to unsigned character maths so wouldn't be surprised if that was where the error lay.

  • Thanks 1

Share this post


Link to post
Share on other sites

Interesting, thanks for sharing.  I tried to do a little math with the timescale, fps, number of frames at around 5 minutes, and I think it worked out to around 4200000 and change, just about 1000x less than a 32-bit overflow (or 500x less than a signed overflow).  Could be a red herring, or I could just be missing a factor of 1000 or 500 somewhere.

 

Anyway, my next step is still to write a script to fix the files and verify the theory is sane and the data is otherwise good.  Then if JagMod is still MIA, I'll try to extend it to a full mov->crg util on my own.  My .mov parser is coming along nicely already.

  • Like 1

Share this post


Link to post
Share on other sites

It turns out writing a python script to fix up the files is not quite as easy as I'd hoped 😑  However, I have one limping along well enough to get the job done now:

 

https://github.com/cubanismo/cinefix

 

This currently only works on chunky files.  I've tested it successfully with quite a few so far, but the encoding & cinepak parameters used for all of them were quite similar, so more testing would be appreciated.  Many fine coasters were made in the development of this script, but I've gotten one very nice Cinepak disk with no audio glitches out of it as a result so far!  See the README.md in github for usage and details.

 

I ended up deciding trying to deduce a pattern for the audio sample placement from the existing files was harder than just figuring out the algorithm for placing them from scratch.  This wasn't that hard.  It's all encapsulated in these two functions:

def setNextAudioSampleTime(self, curSample):
	# XXX assumes 8-bit audio
	sampleDuration = (float32(curSample.size) / self.sampleRate) * self.timescale
	#print("Audio sample duration: " + str(sampleDuration))
	if self.firstAudioSample:
		self.aNextTime += float32(sampleDuration) / float32(2.0)
		self.firstAudioSample = False
	else:
		self.aNextTime = float32(sampleDuration) + self.aNextTime

def calcNextSampleType(self):
	if self.aNextTime < float32(self.vidTime + 1):
		return 'Audio'
	else:
		return 'Video'

And matches the placement JagCinePak uses right up until things go wrong.

 

I think there's room for improvement in the algorithm:  It uses 32-bit floats.  If I use python's built-in float type (double precision), the samples get placed slightly differently, but not necessarily incorrectly (arguably more correct).  The limited precision of single-precision floating point will cause this algorithm to start to break down after about 6 minutes with a timescale of "1000", nearly twice as long with a better timescape of "600".  It probably still works OK, but the samples will start to bounce around more and more.  I'll try to do some testing (going through a lot of CD-R's here) with the higher-precision version, and also try to revive a version I had that worked in relative units and hence kept the magnitude in check better and switch over to that, or implement the Q32.16 fixed-point math that cinepak uses for constant accuracy regardless of the time.  Also, obviously, I should add support for different audio sample resolutions 🙂

 

The script also *almost* supports fixing smooth files, but I sprinted to the end with chunky files because that's all I care about at the moment, and probably all anyone cares about for videos longer than ~10 seconds.  All the sample iterator code should work with both types, I just need to clean up the fixed file generator logic to output a smooth file as well.  At that point, the script could easily be modified to convert to/from chunky/smooth as well.

 

I was hoping it would be obvious why JagCinePak falls over near the ~5 minute mark, but it's still not.  As noted, the precision isn't the best beyond this point,  but I don't know why it completely falls apart.  Still hoping @JagMod will show up and figure that out 🙂

  • Like 3

Share this post


Link to post
Share on other sites

JagCinePak.exe  version 1.53  07/29/20
=====================================
Fixed 32bit overflow. 
    Longer movies with high audio sample rates cause the sample count to exceed 2^32 
Thanks to Cubanismo for finding this bug.

 

Download new version of JagCinepak

  • Like 7
  • Thanks 2

Share this post


Link to post
Share on other sites

So, now the big question Any chance to have a Jaguar Cinepak version for Jaguar GameDrive ? 

  • Like 2

Share this post


Link to post
Share on other sites
8 hours ago, JagMod said:

JagCinePak.exe  version 1.53  07/29/20
=====================================
Fixed 32bit overflow. 
    Longer movies with high audio sample rates cause the sample count to exceed 2^32 
Thanks to Cubanismo for finding this bug.

 

Download new version of JagCinepak

Great, can't wait to test this out! Hopefully no more split segments into 5 minute chunks.

Share this post


Link to post
Share on other sites
4 hours ago, Fredifredo said:

So, now the big question Any chance to have a Jaguar Cinepak version for Jaguar GameDrive ? 

Are the bank switching mechanisms documented yet?  It should be a pretty easy modification to cpkdemo, but I've no way to test right now.  Of course, if CD emu support is working, the current code should work fine.

Share this post


Link to post
Share on other sites

Also, how does one view a Cinepak file on a mostly modern Windows PC? Is there a tool to convert a file back into (for example) MOV or AVI?

Share this post


Link to post
Share on other sites

Not sure why you would want to convert back, since Cinepak is a lossy video codec.

Share this post


Link to post
Share on other sites

Ha! I'm looking for a method to view extracted Cinepak videos on a PC to confirm they were properly extracted. :)

 

Share this post


Link to post
Share on other sites

You could write a program to query/validate the format of the extracted Cinepak videos .

 

*        In the chunky format, the film is broken up into temporal chunks of a
*        specified duration. A chunk table near the start of the film gives the
*        offsets to all chunk records which follow. Every chunk record begins
*        with a sync pattern and contains a sample table giving the offsets
*        relative to the base of the chunk of the audio and video data included
*        within the chunk. 

Share this post


Link to post
Share on other sites
Posted (edited)
26 minutes ago, JagMod said:

You could write a program to query/validate the format of the extracted Cinepak videos 

If you want to see some code that parses chunky & smooth cinepak files, look at my cinefix script above.  If you're extracting cinepak from a cinepak-only disk using the cpkdemo player everyone has used, it'll have a bunch of AIFF and track header junk on it.  Just scan forward from the start of the file until you find the 64-byte sync header of ASCII 1's, and then start reading the film/frame header structure.  Again, see the cinefix code that re-wraps the cinepak file in these headers for details.  If it's some other source (e.g., Battlemorph) you'd have to eyeball the tracks/files in a hex editor first to see if any of the extra padding is present.

 

Quicktime .mov is a moderately more complicated container to write out than the Jaguar smooth/chunky files, but it probably wouldn't be too hard to add code to dump a .mov back out using the sample iterator and header parsing classes in cinefix. Converting to/from each container type should be a lossless operation (sample rate of the sound might be slightly perturbed at worst), so it'd end up no worse than your already-cinepak-encoded source material.

Edited by cubanismo
typos, add note on non-cpkdemo vids
  • Like 1

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...