03-25-2025, 06:14 PM
(This post was last modified: 03-25-2025, 08:21 PM by Petr.
Edit Reason: removed sound cracking when playing in WMP
)
@Steffan-68 
I simply wrote another program to list the contents of the AVI headers. FFMpeg can warn me about chunk displacement, but that was not the case, so when testing in FFMPeg it passed correctly. I finally found the bug, but it took a lot of time. It is commented in the EndAvi SUB, I will leave the previous version above, so you can compare it yourself.
Through further testing I found the following: As already written, the limit of an AVI file IS 4 Gigabytes. So - to be more precise - it is and is not
For programs such as Windows Media Player and Microsoft's "Movies and Shows" program, there is that limit. VLC ignore it and MPC-HC ignore it either. This is because I defined the _Unsigned Long data type, so that when the limit of a 32-bit number _Unsigned Long is exhausted, it does not fall into a negative number like the Long data type, but smoothly overflows and starts from zero. This causes incorrect chunk positions in the file and also incorrect file size in the AVI header. Microsoft players strictly follow these records and therefore if you record a 5 gigabyte file, WMP and other Microsoft players treat it as a 1 gigabyte file and play the first gigabyte of the file.
But then someone in programming thought about more and wrote VLC and MPC-HC and the file is read differently there, so this players play the entire file just fine! Tested on a 7 gigabyte file. And now... i start developing some compression. Slowly!
For this program is needed MP3 file (is used as sound source).
Repair 2, sound is now correct in WMP.
Let the GigaBytes fly!

I simply wrote another program to list the contents of the AVI headers. FFMpeg can warn me about chunk displacement, but that was not the case, so when testing in FFMPeg it passed correctly. I finally found the bug, but it took a lot of time. It is commented in the EndAvi SUB, I will leave the previous version above, so you can compare it yourself.
Through further testing I found the following: As already written, the limit of an AVI file IS 4 Gigabytes. So - to be more precise - it is and is not
For programs such as Windows Media Player and Microsoft's "Movies and Shows" program, there is that limit. VLC ignore it and MPC-HC ignore it either. This is because I defined the _Unsigned Long data type, so that when the limit of a 32-bit number _Unsigned Long is exhausted, it does not fall into a negative number like the Long data type, but smoothly overflows and starts from zero. This causes incorrect chunk positions in the file and also incorrect file size in the AVI header. Microsoft players strictly follow these records and therefore if you record a 5 gigabyte file, WMP and other Microsoft players treat it as a 1 gigabyte file and play the first gigabyte of the file.But then someone in programming thought about more and wrote VLC and MPC-HC and the file is read differently there, so this players play the entire file just fine! Tested on a 7 gigabyte file. And now... i start developing some compression. Slowly!
For this program is needed MP3 file (is used as sound source).
Repair 2, sound is now correct in WMP.
Code: (Select All)
Dim ms As _MEM
Dim snd As Long
snd = _SndOpen("slunce.mp3") ' MP3 file, from which we read raw samples
' -------------------------
' Structure Definitions
' -------------------------
Type AVIMainHeader
microSecPerFrame As Long ' Microseconds per frame
maxBytesPerSec As Long ' Max data rate (bytes per second)
paddingGranularity As Long
flags As Long ' e.g. AVIF_HASINDEX
totalFrames As Long
initialFrames As Long
streams As Long ' number of streams, e.g. 2 (video+audio)
suggestedBufferSize As Long
width As Long
height As Long
reserved As String * 16
End Type
Type AVIStreamHeader
fccType As String * 4 ' "vids" or "auds"
fccHandler As String * 4
flags As Long
priority As Integer
language As Integer
initialFrames As Long
scale As Long
rate As Long
start As Long
length As Long
suggestedBufferSize As Long
quality As Long
sampleSize As Long
frameLeft As Long
frameTop As Long
frameRight As Long
frameBottom As Long
End Type
Type BITMAPINFOHEADER
size As Long
width As Long
height As Long
planes As Integer
bitCount As Integer
compression As Long
sizeImage As Long
xPelsPerMeter As Long
yPelsPerMeter As Long
clrUsed As Long
clrImportant As Long
End Type
Type WAVEFORMATEX
wFormatTag As Integer
nChannels As Integer
nSamplesPerSec As Long
nAvgBytesPerSec As Long
nBlockAlign As Integer
wBitsPerSample As Integer
cbSize As Integer
End Type
Type ChunkIndex
chunkID As String * 4
flags As Long
offset As _Unsigned Long ' offset from start of 'movi'
size As Long
End Type
' -------------------------
' SHARED variables
' -------------------------
Dim Shared aviFileNum As Integer
Dim Shared riffSizePos As _Unsigned Long, hdrlSizePos As _Unsigned Long
Dim Shared moviSizePos As _Unsigned Long, moviDataStart As _Unsigned Long
Dim Shared mainHdrPos As _Unsigned Long
Dim Shared vidStrhPos As _Unsigned Long
Dim Shared audStrhPos As _Unsigned Long
Dim Shared audStrh As AVIStreamHeader
Dim Shared shMainHdr As AVIMainHeader
Dim Shared shVidStrh As AVIStreamHeader
Dim Shared totalFrames As Long, totalAudioSamples As Long
ReDim Shared idxArr(1 To 10000) As ChunkIndex
Dim Shared idxCount As Long
' -------------------------
' Parameters
' -------------------------
Const vidFPS = 25
Const bytesPerPixel = 4
Dim Shared frameWidth As Long: frameWidth = 320
Dim Shared frameHeight As Long: frameHeight = 240
Const sampleRate = 44100
' -------------------------
' Main program
' -------------------------
Cls
idxCount = 0
totalFrames = 0
totalAudioSamples = 0
' 1) Initialize AVI
StartAvi "VideoTest5.avi"
Screen _NewImage(frameWidth, frameHeight, 32)
' 2) Recording loop
Dim Shared As Long x, img
Dim Shared frameData As String
Dim Shared memData As _MEM
Dim Shared audioSamplesPerFrame As Long
audioSamplesPerFrame = sampleRate \ vidFPS
Dim Shared audioData As String
Dim Shared iSample As Long
Dim Shared As Integer sampleL, sampleR
Dim StartTimer As Single
StartTimer = Timer
Dim afs As _Unsigned Long
' Audio reading
ms = _MemSound(snd, 0)
Dim audL(frameWidth) As Single
Dim audR(frameWidth) As Single
Dim au As Long, xx As Long
Dim SampleL1 As Single, SampleR1 As Single
Dim preDest As Long
Dim uVal As Long
Dim jN As Single
jN = .011
img = _NewImage(frameWidth, frameHeight, 32)
preDest = _Dest
VisStep = _Ceil(audioSamplesPerFrame / frameWidth)
Do While InKey$ = "" Or au > ms.SIZE - 8
totalFrames = totalFrames + 1
' --- Generate video frame ---
_Dest img
Cls
deltaX = (frameWidth - xx) \ 2
For x = 1 To xx
' Some sample visual effect
Line (deltaX + x, frameHeight / 2 - 150 * audL(x - 1))-(deltaX + x + 1, frameHeight / 2 - 150 * audL(x)), _RGB32(Sin(j) * 255, Sin(j + .45) * 255, Sin(j + .9) * 255), BF
j = j + jN
Line (deltaX + x, frameHeight / 2 + 150 * audR(x - 1))-(deltaX + x + 1, frameHeight / 2 + 150 * audR(x)), _RGB32(Cos(j) * 255, Cos(j + .45) * 255, Cos(j + .9) * 255), BF
j = j + jN
Next x
If Abs(j) > 6.28 Then jN = jN * -1
frameData = Space$(frameWidth * frameHeight * bytesPerPixel)
memData = _MemImage(img)
_MemGet memData, memData.OFFSET, frameData
_MemFree memData
_Dest preDest
' --- Generate audio block ---
audioData = ""
xx = 0
For iSample = 1 To audioSamplesPerFrame
SampleL1 = _MemGet(ms, ms.OFFSET + au, Single)
SampleR1 = _MemGet(ms, ms.OFFSET + au + 4, Single)
_SndRaw SampleL1, SampleR1
sampleL = SampleL1 * 32767
sampleR = SampleR1 * 32767
If iSample Mod VisStep = 0 Then
audL(xx) = SampleL1
audR(xx) = SampleR1
xx = xx + 1
End If
au = au + 8
If au > ms.SIZE - 8 Then Exit Do
uVal = sampleL And &HFFFF
audioData = audioData + Chr$(uVal And &HFF) + Chr$((uVal \ 256) And &HFF)
uVal = sampleR And &HFFFF
audioData = audioData + Chr$(uVal And &HFF) + Chr$((uVal \ 256) And &HFF)
Next
totalAudioSamples = totalAudioSamples + audioSamplesPerFrame * 2
' --- Write chunk (video+audio) ---
CreateAviData frameData, audioData
_PutImage , img, 0
Locate 1
afs = LOF(aviFileNum)
Print "Press any key to stop generating AVI. File size: "; afs; " Duration: "; Int(Timer - StartTimer)
_Display
_Limit vidFPS + 1
If _Exit Then Exit Do
Do Until _SndRawLen < .1
Loop
Loop
_Dest preDest
_AutoDisplay
Print "Closing AVI, please wait!"
_MemFree ms
_SndClose snd
' 3) Finalize AVI (update total frames etc.)
EndAvi
Print "Done: 'VideoTest5.avi'"
End
' ------------------------------------------------------
' Sub StartAvi – opens file, writes base headers, prepares LIST movi
' ------------------------------------------------------
Sub StartAvi (aviName As String)
Dim dummyLong As Long: dummyLong = 0
aviFileNum = FreeFile
If _FileExists(aviName) Then Kill aviName
Open aviName For Binary As #aviFileNum
' "RIFF"
Dim strRIFF As String
strRIFF = "RIFF"
Put #aviFileNum, , strRIFF
riffSizePos = LOF(aviFileNum) + 1
Put #aviFileNum, , dummyLong
Dim strAVI As String
strAVI = "AVI "
Put #aviFileNum, , strAVI
' "LIST" (hdrl)
Dim strLIST As String
strLIST = "LIST"
Put #aviFileNum, , strLIST
hdrlSizePos = LOF(aviFileNum) + 1
Put #aviFileNum, , dummyLong
Dim strHdrl As String
strHdrl = "hdrl"
Put #aviFileNum, , strHdrl
' "avih" chunk
Dim strAvih As String
strAvih = "avih"
Put #aviFileNum, , strAvih
Dim avihChunkSize As Long: avihChunkSize = 56
Put #aviFileNum, , avihChunkSize
' Prepare main AVI header
shMainHdr.microSecPerFrame = 1000000 \ vidFPS
' Critically important: set a realistic data rate:
shMainHdr.maxBytesPerSec = frameWidth * frameHeight * bytesPerPixel * vidFPS
shMainHdr.paddingGranularity = 0
shMainHdr.flags = &H110 ' AVIF_HASINDEX + AVIF_ISINTERLEAVED
shMainHdr.totalFrames = 0 ' will fill real value in EndAvi
shMainHdr.initialFrames = 0
shMainHdr.streams = 2 ' video+audio
shMainHdr.suggestedBufferSize = frameWidth * frameHeight * bytesPerPixel
shMainHdr.width = frameWidth
shMainHdr.height = frameHeight
shMainHdr.reserved = String$(16, Chr$(0))
mainHdrPos = LOF(aviFileNum) + 1
Put #aviFileNum, , shMainHdr
' LIST strl (video)
strLIST = "LIST"
Put #aviFileNum, , strLIST
Dim listVidSizePos As _Unsigned Long
listVidSizePos = LOF(aviFileNum) + 1
Put #aviFileNum, , dummyLong
Dim strlVid As String
strlVid = "strl"
Put #aviFileNum, , strlVid
' "strh" video
Dim strhVid As String
strhVid = "strh"
Put #aviFileNum, , strhVid
Dim strhVidSize As Long: strhVidSize = 64
Put #aviFileNum, , strhVidSize
' Prepare video stream header
shVidStrh.fccType = "vids"
shVidStrh.fccHandler = "DIB "
shVidStrh.flags = 0
shVidStrh.priority = 0
shVidStrh.language = 0
shVidStrh.initialFrames = 0
shVidStrh.scale = 1
shVidStrh.rate = vidFPS
shVidStrh.start = 0
shVidStrh.length = 0 ' real value in EndAvi
shVidStrh.suggestedBufferSize = frameWidth * frameHeight * bytesPerPixel
shVidStrh.quality = -1
shVidStrh.sampleSize = 0
shVidStrh.frameLeft = 0
shVidStrh.frameTop = 0
shVidStrh.frameRight = frameWidth
shVidStrh.frameBottom = frameHeight
vidStrhPos = LOF(aviFileNum) + 1
Put #aviFileNum, , shVidStrh
' "strf" video => BITMAPINFOHEADER
Dim strfVid As String
strfVid = "strf"
Put #aviFileNum, , strfVid
Dim strfVidSize As Long: strfVidSize = 40
Put #aviFileNum, , strfVidSize
Dim bmpInfo As BITMAPINFOHEADER
bmpInfo.size = 40
bmpInfo.width = frameWidth
bmpInfo.height = -frameHeight ' top–down
bmpInfo.planes = 1
bmpInfo.bitCount = 32
bmpInfo.compression = 0
bmpInfo.sizeImage = frameWidth * frameHeight * bytesPerPixel
bmpInfo.xPelsPerMeter = 0
bmpInfo.yPelsPerMeter = 0
bmpInfo.clrUsed = 0
bmpInfo.clrImportant = 0
Put #aviFileNum, , bmpInfo
Dim currPos As _Unsigned Long
currPos = LOF(aviFileNum) + 1
Dim calcListVidSize As _Unsigned Long
calcListVidSize = currPos - listVidSizePos - 4
Seek #aviFileNum, listVidSizePos
Put #aviFileNum, , calcListVidSize
Seek #aviFileNum, currPos
' LIST strl (audio)
Put #aviFileNum, , strLIST
Dim listAudSizePos As _Unsigned Long
listAudSizePos = LOF(aviFileNum) + 1
Put #aviFileNum, , dummyLong
Dim strlAud As String
strlAud = "strl"
Put #aviFileNum, , strlAud
Dim strhAud As String
strhAud = "strh"
Put #aviFileNum, , strhAud
Dim strhAudSize As Long: strhAudSize = 64
Put #aviFileNum, , strhAudSize
' Prepare audio stream header
audStrh.fccType = "auds"
audStrh.fccHandler = String$(4, 0)
audStrh.flags = 0
audStrh.priority = 0
audStrh.language = 0
audStrh.initialFrames = 0
audStrh.scale = 1
audStrh.rate = sampleRate
audStrh.start = 0
audStrh.length = 0 ' set final in EndAvi
audStrh.suggestedBufferSize = sampleRate * 4
audStrh.quality = -1
audStrh.sampleSize = 4 ' stereo 16-bit = 4 bytes/frame
audStrh.frameLeft = 0
audStrh.frameTop = 0
audStrh.frameRight = 0
audStrh.frameBottom = 0
audStrhPos = LOF(aviFileNum) + 1
Put #aviFileNum, , audStrh
Dim strfAud As String
strfAud = "strf"
Put #aviFileNum, , strfAud
' For PCM => 16 bytes waveformat
Dim wfSize As Long
wfSize = 16
Put #aviFileNum, , wfSize
Dim wf As WAVEFORMATEX
wf.wFormatTag = 1
wf.nChannels = 2
wf.nSamplesPerSec = sampleRate
wf.nBlockAlign = wf.nChannels * (16 \ 8)
wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign
wf.wBitsPerSample = 16
wf.cbSize = 0
Put #aviFileNum, , wf
currPos = LOF(aviFileNum) + 1
Dim calcListAudSize As _Unsigned Long
calcListAudSize = currPos - listAudSizePos - 4
Seek #aviFileNum, listAudSizePos
Put #aviFileNum, , calcListAudSize
Seek #aviFileNum, currPos
' LIST movi
Put #aviFileNum, , strLIST
moviSizePos = LOF(aviFileNum) + 1
Put #aviFileNum, , dummyLong
Dim strMovi As String
strMovi = "movi"
Put #aviFileNum, , strMovi
moviDataStart = LOF(aviFileNum) + 1
End Sub
' ------------------------------------------------------
' Sub CreateAviData – writes video chunk (00db) + audio chunk (01wb)
' ------------------------------------------------------
Sub CreateAviData (frameData As String, audioData As String)
Dim chunkOffset As _Unsigned Long
chunkOffset = LOF(aviFileNum) - moviDataStart + 1
' Video chunk
Dim vidChunkID As String
vidChunkID = "00db"
Put #aviFileNum, , vidChunkID
Dim frameLen As Long
frameLen = Len(frameData)
Put #aviFileNum, , frameLen
Put #aviFileNum, , frameData
If (frameLen Mod 2) <> 0 Then
Dim padV As String
padV = Chr$(0)
Put #aviFileNum, , padV
End If
idxCount = idxCount + 1
If UBound(idxArr) < idxCount Then ReDim _Preserve idxArr(1 To idxCount + 1000) As ChunkIndex
idxArr(idxCount).chunkID = vidChunkID
idxArr(idxCount).flags = &H10 ' keyframe
idxArr(idxCount).offset = chunkOffset
idxArr(idxCount).size = frameLen
' Audio chunk
Dim chunkOffsetA As _Unsigned Long
chunkOffsetA = LOF(aviFileNum) - moviDataStart + 1
Dim audChunkID As String
audChunkID = "01wb"
Put #aviFileNum, , audChunkID
Dim audioLen As Long
audioLen = Len(audioData)
Put #aviFileNum, , audioLen
Put #aviFileNum, , audioData
If (audioLen Mod 2) <> 0 Then
Dim padA As String
padA = Chr$(0)
Put #aviFileNum, , padA
End If
idxCount = idxCount + 1
If UBound(idxArr) < idxCount Then ReDim _Preserve idxArr(1 To idxCount + 1000) As ChunkIndex
idxArr(idxCount).chunkID = audChunkID
idxArr(idxCount).flags = 0
idxArr(idxCount).offset = chunkOffsetA
idxArr(idxCount).size = audioLen + 1
End Sub
' ------------------------------------------------------
' Sub EndAvi – finalize: update frame counts, sizes, index
' ------------------------------------------------------
Sub EndAvi
' Fill the real total frames into mainHdr + video strh
shMainHdr.totalFrames = totalFrames
shVidStrh.length = totalFrames
' For stereo: totalAudioSamples are L+R separately, so we do \2
audStrh.length = totalAudioSamples \ 2
' Rewrite them at known positions
Put #aviFileNum, mainHdrPos, shMainHdr
Put #aviFileNum, vidStrhPos, shVidStrh
Put #aviFileNum, audStrhPos, audStrh
' Now fix sizes of movi, riff, hdrl
Dim currPos As _Unsigned Long
currPos = LOF(aviFileNum) + 1
' movi size
Dim moviSize As _Unsigned Long
moviSize = (currPos - moviSizePos) - 4
Put #aviFileNum, moviSizePos, moviSize
Seek #aviFileNum, currPos
' RIFF size
Dim riffSize As _Unsigned Long
riffSize = currPos - 8
Put #aviFileNum, riffSizePos, riffSize
Seek #aviFileNum, currPos
' Write idx1
Dim strIdx As String
strIdx = "idx1"
Put #aviFileNum, , strIdx
Dim idxSize As Long
idxSize = idxCount * 16
Put #aviFileNum, , idxSize
Dim Nsize As _Unsigned Long
Nsize = LOF(aviFileNum) + 1
' hdrl size
Dim hdrlSize As _Unsigned Long
hdrlSize = 310 'valid just for this format (32bit rgba, 16bit PCM stereo) THIS WAS THE WMP BUG!!!
Put #aviFileNum, hdrlSizePos, hdrlSize
Seek #aviFileNum, Nsize
' Write all index entries
Dim i As Long
For i = 1 To idxCount
Put #aviFileNum, , idxArr(i).chunkID
Put #aviFileNum, , idxArr(i).flags
Put #aviFileNum, , idxArr(i).offset
Put #aviFileNum, , idxArr(i).size
Next i
Close #aviFileNum
End Sub
Let the GigaBytes fly!

