Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
AVI file format
#11
I'm glad for your responses. Believe me, I'm trying to embellish it somehow. I have a version where the output works in VLC player and MPC-HC, but unfortunately Windows Media Player just stands up and rejects the output file. I've been looking for an error in the structure for several hours - but if FFMPEG is happy with it, I really don't know what Windows Media Player doesn't like. Well, however, here you have such an intermediate version (let's say a hot program directly from the keyboard). Recording an AVI file is limited by the length of the song this time, or it can be stopped at any time by pressing any key.

However, it is necessary to underline a few fundamental things. Since we are dealing with huge numbers here, the LOF function that the program refers to has a limit of 9 GigaBytes. Take this into account when using this program. Another limit is the file system of your operating system. If you use FAT32, you will reach the limit at a size of 4 Gigabyte, while with NTFS and EXT4, LOF will fail later (limit 16 TB). So I recommend, if you really want crazy sizes, to split your files according to these operating system limits. I will try some light compression, but first I will try to make this compatible with WMP as well.

You NEED MP3 file  Big Grin

Code: (Select All)

Dim ms As _MEM
Dim snd As Long
snd = _SndOpen("b.mp3") '<--- place here MP3 (must be MP3!) file


' ======================================================
' Dynamic creation of AVI file with structures and correct offsets
' + updating the total frame count upon finishing recording
' ======================================================

' -------------------------
' Structure Definitions
' -------------------------
Type AVIMainHeader
    microSecPerFrame As Long ' Microseconds per frame
    maxBytesPerSec As Long ' Maximum data rate (bytes per second)
    paddingGranularity As Long ' Padding granularity (for alignment)
    flags As Long ' AVI flags (e.g., HASINDEX)
    totalFrames As Long ' Total number of frames in the video
    initialFrames As Long ' Initial frames (typically 0)
    streams As Long ' Number of streams (video, audio, etc.)
    suggestedBufferSize As Long ' Suggested buffer size for playback
    width As Long ' Video width in pixels
    height As Long ' Video height in pixels
    reserved As String * 16 ' Reserved bytes (set to 0)
End Type

Type AVIStreamHeader
    fccType As String * 4 ' Stream type (e.g., "vids" for video, "auds" for audio)
    fccHandler As String * 4 ' Codec handler (e.g., "DIB " for uncompressed video)
    flags As Long ' Stream flags
    priority As Integer ' Priority (unused)
    language As Integer ' Language code (unused)
    initialFrames As Long ' Initial frames (unused)
    scale As Long ' Time scale for the stream
    rate As Long ' Data rate (scale/rate = frames per second for video)
    start As Long ' Start time (usually 0)
    length As Long ' Length of the stream (in time units)
    suggestedBufferSize As Long ' Suggested buffer size for the stream
    quality As Long ' Quality indicator (-1 for default)
    sampleSize As Long ' Sample size (0 for video, nonzero for audio)
    frameLeft As Long ' For video: left coordinate of the source rectangle
    frameTop As Long ' For video: top coordinate of the source rectangle
    frameRight As Long ' For video: right coordinate of the source rectangle
    frameBottom As Long ' For video: bottom coordinate of the source rectangle
End Type

Type BITMAPINFOHEADER
    size As Long ' Size of this header (40 bytes)
    width As Long ' Image width in pixels
    height As Long ' Image height (negative for top–down)
    planes As Integer ' Number of color planes (must be 1)
    bitCount As Integer ' Bits per pixel (24 for 24-bit, 32 for 32-bit)
    compression As Long ' Compression method (0 = BI_RGB for uncompressed)
    sizeImage As Long ' Size of the image data in bytes
    xPelsPerMeter As Long ' Horizontal resolution (pixels per meter)
    yPelsPerMeter As Long ' Vertical resolution (pixels per meter)
    clrUsed As Long ' Number of colors used (0 = all)
    clrImportant As Long ' Number of important colors (0 = all)
End Type

Type WAVEFORMATEX
    wFormatTag As Integer ' Audio format code (1 = PCM)
    nChannels As Integer ' Number of audio channels (2 for stereo)
    nSamplesPerSec As Long ' Sample rate in Hz
    nAvgBytesPerSec As Long ' Average bytes per second
    nBlockAlign As Integer ' Block alignment in bytes
    wBitsPerSample As Integer ' Bits per sample (16 for 16-bit PCM)
    cbSize As Integer ' Extra size (0 for PCM)
End Type

Type ChunkIndex
    chunkID As String * 4 ' Chunk identifier (e.g., "00db" for video, "01wb" for audio)
    flags As Long ' Flags (e.g., keyframe flag)
    offset As _Unsigned Long ' Offset of chunk data from start of 'movi' section
    size As Long ' Size of chunk data in bytes (could also be _Unsigned Long if needed)
End Type

' -------------------------
' SHARED Variables
' -------------------------
Dim Shared aviFileNum As Integer

' Positions for updating headers and stream information - now declared as _unsigned long
Dim Shared riffSizePos As _Unsigned Long, hdrlSizePos As _Unsigned Long
Dim Shared moviSizePos As _Unsigned Long, moviDataStart As _Unsigned Long

' Save the file position where the MainHeader is written
Dim Shared mainHdrPos As _Unsigned Long

' Save the file position for the video stream header
Dim Shared vidStrhPos As _Unsigned Long

' For the audio stream – position and copy of the stream header
Dim Shared audStrhPos As _Unsigned Long
Dim Shared audStrh As AVIStreamHeader

' Shared copies of structures that will be updated upon finish
Dim Shared shMainHdr As AVIMainHeader
Dim Shared shVidStrh As AVIStreamHeader

' Counters for video frames and audio samples
Dim Shared totalFrames As Long, totalAudioSamples As Long

' Array of chunk indices
Dim Shared idxArr(1 To 10000) As ChunkIndex
Dim Shared idxCount As Long

' -------------------------
' Parameters
' -------------------------
Const vidFPS = 20 ' Frames per second
Const bytesPerPixel = 4 ' Each pixel in a 32-bit image (BGRA) takes 4 bytes
Dim Shared frameWidth As Long: frameWidth = 640
Dim Shared frameHeight As Long: frameHeight = 480
Const sampleRate = 44100 ' Audio sample rate

' -------------------------
' Main Program
' -------------------------
Cls

idxCount = 0
totalFrames = 0
totalAudioSamples = 0

' 1) Initialize AVI file
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

' Variable for file size from LOF (now _unsigned long)
Dim afs As _Unsigned Long

' --- Your video and audio loop starts here ---
ms = _MemSound(snd, 0)
Dim audL(frameWidth) As Single
Dim audR(frameWidth) As Single
Dim au As Long, xx As Long
Dim As Single SampleL1, SampleR1

Do While InKey$ = "" Or au > ms.SIZE - 8
    totalFrames = totalFrames + 1

    ' --- Generate video frame ---
    img = _NewImage(frameWidth, frameHeight, 32)
    Dim preDest As Long
    preDest = _Dest
    _Dest img
    Cls
    deltaX = (frameWidth - xx) \ 2
    For x = 0 To xx
        Line (deltaX + x, frameHeight / 2)-(deltaX + x, frameHeight / 2 - 150 * audL(x)), _RGB32(Sin(j) * 255, Sin(j + .45) * 255, Sin(j + .9) * 255), BF
        Line (deltaX + x, frameHeight / 2)-(deltaX + x, frameHeight / 2 + 150 * audR(x)), _RGB32(Cos(j) * 255, Cos(j + .45) * 255, Cos(j + .9) * 255), BF
        j = j + .015
    Next x

    frameData = Space$(frameWidth * frameHeight * bytesPerPixel)
    memData = _MemImage(img)
    _MemGet memData, memData.OFFSET, frameData
    _MemFree memData
    _Dest preDest

    ' --- Generate audio block ---
    audioData = ""
    xx = 0
    VisStep = _Ceil(audioSamplesPerFrame / frameWidth)
    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 ' if music ends, finish loop
        Dim uVal As Long
        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 ---
    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)
    _FreeImage img
    _Display
    Do Until _SndRawLen < .1
    Loop

    _Limit 20
Loop
_Dest preDest
_AutoDisplay
Print "Closing AVI, please wait!"
' --- End of video and audio loop ---
_MemFree ms
_SndClose snd
' 3) Finalize AVI (update frame counts etc.)
EndAvi

Print "Done: 'VideoTest5.avi'"
Sleep
End

' -----------------------------------------------------------------
' Sub StartAvi – Opens file, writes headers and prepares LIST movi
' -----------------------------------------------------------------
Sub StartAvi (AviName As String)
    ' dummyLong is a placeholder for 32-bit chunk size; Long is sufficient
    Dim dummyLong As Long
    dummyLong = 0

    aviFileNum = FreeFile
    If _FileExists(AviName) Then Kill AviName
    Open AviName For Binary As #aviFileNum

    ' 1) RIFF chunk
    Dim strRIFF As String
    strRIFF = "RIFF"
    Put #aviFileNum, , strRIFF
    riffSizePos = LOF(aviFileNum) + 1 ' _unsigned long
    Put #aviFileNum, , dummyLong
    Dim strAVI As String
    strAVI = "AVI "
    Put #aviFileNum, , strAVI

    ' 2) LIST hdrl chunk
    Dim strLIST As String
    strLIST = "LIST"
    Put #aviFileNum, , strLIST
    hdrlSizePos = LOF(aviFileNum) + 1 ' _unsigned long
    Put #aviFileNum, , dummyLong
    Dim strHdrl As String
    strHdrl = "hdrl"
    Put #aviFileNum, , strHdrl

    ' 3) avih chunk (Main AVI header)
    Dim strAvih As String
    strAvih = "avih"
    Put #aviFileNum, , strAvih
    Dim avihChunkSize As Long
    avihChunkSize = 56
    Put #aviFileNum, , avihChunkSize

    ' Fill shMainHdr with main header information
    shMainHdr.microSecPerFrame = 1000000 \ vidFPS
    shMainHdr.maxBytesPerSec = frameWidth * frameHeight * bytesPerPixel * vidFPS
    shMainHdr.paddingGranularity = 0
    shMainHdr.flags = &H110 ' AVIF_HASINDEX + AVIF_ISINTERLEAVED
    shMainHdr.totalFrames = 0
    shMainHdr.initialFrames = 0
    shMainHdr.streams = 2
    shMainHdr.suggestedBufferSize = frameWidth * frameHeight * bytesPerPixel
    shMainHdr.width = frameWidth
    shMainHdr.height = frameHeight
    shMainHdr.reserved = String$(16, Chr$(0))

    mainHdrPos = LOF(aviFileNum) + 1 ' _unsigned long
    Put #aviFileNum, , shMainHdr

    ' --- Video stream (LIST strl) ---
    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

    ' Video stream header (strh)
    Dim strhVid As String
    strhVid = "strh"
    Put #aviFileNum, , strhVid
    Dim strhVidSize As Long
    strhVidSize = 64
    Put #aviFileNum, , strhVidSize

    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
    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 ' _unsigned long
    Put #aviFileNum, , shVidStrh

    ' Video format chunk (strf) using 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 ' Negative height indicates top–down image
    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

    ' --- Audio stream (LIST strl) ---
    Put #aviFileNum, , strLIST
    Dim listAudSizePos As _Unsigned Long
    listAudSizePos = LOF(aviFileNum) + 1
    Put #aviFileNum, , dummyLong
    Dim strlAud As String
    strlAud = "strl"
    Put #aviFileNum, , strlAud

    ' Audio stream header (strh)
    Dim strhAud As String
    strhAud = "strh"
    Put #aviFileNum, , strhAud
    Dim strhAudSize As Long
    strhAudSize = 64
    Put #aviFileNum, , strhAudSize

    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
    audStrh.suggestedBufferSize = sampleRate * 4
    audStrh.quality = -1
    audStrh.sampleSize = 4
    audStrh.frameLeft = 0
    audStrh.frameTop = 0
    audStrh.frameRight = 0
    audStrh.frameBottom = 0

    audStrhPos = LOF(aviFileNum) + 1 ' _unsigned long
    Put #aviFileNum, , audStrh

    Dim strfAud As String
    strfAud = "strf"
    Put #aviFileNum, , strfAud

    ' Corrected WAVEFORMATEX chunk size to 18 (2 extra bytes -> cbSize)
    Dim wfSize As Long
    wfSize = 18
    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 chunk ---
    Put #aviFileNum, , strLIST
    moviSizePos = LOF(aviFileNum) + 1 ' _unsigned long
    Put #aviFileNum, , dummyLong
    Dim strMovi As String
    strMovi = "movi"
    Put #aviFileNum, , strMovi
    moviDataStart = LOF(aviFileNum) + 1
End Sub

' ---------------------------------------------------------------------
' Sub CreateAviData – Writes video chunk (00db) and audio chunk (01wb)
' ---------------------------------------------------------------------
Sub CreateAviData (frameData As String, audioData As String)
    Dim padA As String
    padA = Chr$(0)

    ' --- Video chunk ---
    Dim chunkOffset As _Unsigned Long
    chunkOffset = LOF(aviFileNum) - moviDataStart + 1
    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
    idxArr(idxCount).chunkID = vidChunkID
    idxArr(idxCount).flags = &H10 ' key frame flag
    idxArr(idxCount).offset = chunkOffset
    idxArr(idxCount).size = frameLen
    If UBound(idxArr) <= idxCount Then ReDim _Preserve idxArr(idxCount + 1000) As ChunkIndex

    ' --- 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
        Put #aviFileNum, , padA
    End If

    idxCount = idxCount + 1
    idxArr(idxCount).chunkID = audChunkID
    idxArr(idxCount).flags = 0
    idxArr(idxCount).offset = chunkOffsetA
    idxArr(idxCount).size = audioLen
End Sub

' ----------------------------------------------------------------------------------------
' Sub EndAvi – Finalizes AVI: updates totalFrames, movi size, RIFF size, and writes index
' ----------------------------------------------------------------------------------------
Sub EndAvi
    ' 1) Update shMainHdr with the actual totalFrames
    shMainHdr.totalFrames = totalFrames

    ' 2) Update video stream header length with totalFrames
    shVidStrh.length = totalFrames

    ' 2a) Update audio stream header – length is number of samples per channel
    '    (totalAudioSamples contains samples for both channels)
    audStrh.length = totalAudioSamples \ 2

    ' --- Rewrite AVIMainHeader (avih) ---
    Seek #aviFileNum, mainHdrPos
    Put #aviFileNum, , shMainHdr

    ' --- Rewrite AVIStreamHeader (video) ---
    Seek #aviFileNum, vidStrhPos
    Put #aviFileNum, , shVidStrh

    ' --- Rewrite AVIStreamHeader (audio) ---
    Seek #aviFileNum, audStrhPos
    Put #aviFileNum, , audStrh

    ' 3) Adjust LIST movi size
    Dim currPos As _Unsigned Long
    currPos = LOF(aviFileNum) + 1

    Dim moviSize As _Unsigned Long
    moviSize = currPos - moviSizePos - 4
    Seek #aviFileNum, moviSizePos
    Put #aviFileNum, , moviSize
    Seek #aviFileNum, currPos

    ' 4) RIFF size (total file size minus 8)
    Dim riffSize As _Unsigned Long
    riffSize = currPos - 8 ' This should be correct; adjust if necessary for 0-index differences
    Seek #aviFileNum, riffSizePos
    Put #aviFileNum, , riffSize
    Seek #aviFileNum, currPos

    ' 5) Write index (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

    Dim hdrlSize As _Unsigned Long
    hdrlSize = currPos - hdrlSizePos - 4
    Seek #aviFileNum, hdrlSizePos
    Put #aviFileNum, , hdrlSize
    Seek #aviFileNum, Nsize

    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


Reply
#12
Thanks @Petr I will give this a try when I am back at the PC!
Reply
#13
Max. AVI size is 4GB:
In the avi-header moviSize, idxSize, etc. are all 32 bit long.
Would need space in the header for Int64 to allow larger files.
See my version above for max speed/size limitations
45y and 2M lines of MBASIC>BASICA>QBASIC>QBX>QB64 experience
Reply
#14
@mdijkens

Yeah, I wrote some pretty stupid stuff, true. The limit is clearly given as you write. I tried your version of the program, it works fine. But my latest version is annoying me, WMP still resists. Where the hell is the bug...


Reply
#15
(03-25-2025, 09:20 AM)Petr Wrote: @mdijkens

Yeah, I wrote some pretty stupid stuff, true. The limit is clearly given as you write. I tried your version of the program, it works fine. But my latest version is annoying me, WMP still resists. Where the hell is the bug...

Just tried it.

VLC    OK
MPC   OK
WMP  OK

Media playback   NOP

I use Windows 10 und QB64PE V 4.1

   
Reply
#16
@Steffan-68 Big Grin

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 Smile 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!

             Angel


Reply
#17
Hey @Petr, do you have any code that can read a single frame from an AVI file into 32-bit image? 

I am thinking just a couple functions, something like 

CountAviFrames&(AviFileName$) 
would return the total number of frames present in file AviFileName$

and

GetAviFrame(AviFileName$, FrameNumber&, FrameImage&) 
would retrieve frame number FrameNumber& from file AviFileName$ 
and return it by reference in image FrameImage&



I don't currently need audio, I'm just looking for a way to programmatically retrieve a single frame at a time as a 32-bit image that I can then process using QB64PE's regular graphics commands.

Maybe later it would be nice to be able to write the image back to the AVI file, but I don't need that right now.

Any help appreciated!
Reply
#18
In this AVI format that is here, an uncompressed image format (basically RAW) is used, just a pure copy of the memory that is stored as data. It is madness to compress it in software on the fly. 1920x1980 pixels and you have one twenty-fifth of a second to process including writing data, because you have 25 frames (and in the USA 30) per second according to the standard. As I admit, I got myself into a trap.

I will write a program that will create what you want (for uncompressed AVI here) later, I am currently solving something else. It will not be anything complicated, everything you need is contained in the AVI header, then you just need to read the correct offsets in the file. And for cases of changing frames in AVI it will be a little more complicated, because it is necessary to keep the same size of the image as the original, so it will eventually have to be stretched or reduced (via _NewImage and _PutImage it will be an easy thing). But all this will work only and only for this format. Then you need to consider whether to allow conversion from a different resolution or strictly refuse it, if you allow it, whether with image deformation or in such a way that the aspect ratio of the new frame is preserved - and how - whether to reduce, enlarge, or crop. That is already a complex program and I do not plan to do that now.
If you want it with compression, then we really cannot avoid using ffmpeg. This uncompressed file could then only serve as a template.

So far, I am considering compression in such a way that the recording is saved to multiple AVI files in an uncompressed format and then in the next step compression would run from the already saved content.
When I think about it, a significant speedup would be to run the compression as the second process after the 1GB uncompressed block is finished. That way, you would compress the 1GB in ffmpeg and simultaneously saving the other file as uncompressed.

I will deal with it later.


Reply
#19
That all sounds great and makes sense. It doesn't have to do the processing in realtime, just read in an existing AVI file one frame at a time, copy each frame to a 32-bit image (no resizing or anything) that can be drawn on or processed with QB64PE's graphics commands, and then written to a new AVI file #2. So the original AVI file does not need to be changed. 

The compression is not important, I have utilities that can help with that for now. 

I understand you are busy, not expecting anything or asking for anything right away, but if you can eventually get around to writing any code that can help with that, that would be great. 

Thanks again for all you have already done, and good luck with your other projects!
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  APNG File Format Petr 5 544 11-20-2025, 02:32 PM
Last Post: ahenry3068
  GIF89a File Format Petr 6 1,320 03-04-2025, 01:20 AM
Last Post: a740g
  PCX file format Petr 13 3,386 03-01-2025, 10:52 PM
Last Post: Petr
  BMP File format Petr 8 1,749 02-23-2025, 07:54 PM
Last Post: Petr

Forum Jump:


Users browsing this thread: 1 Guest(s)