Jump to content
Sign in to follow this  
gil900

Method how to play mp3 files with windows API and without any mp3 codec

Recommended Posts

gil900

Hello,

Today I did a lot of research to know if it is possible to play mp3 file without using any DLL. Just use the Windows API.

And I am happy to inform you in that it's possible! I managed to do it.

First of all to understand why it works, you have to understand what is the wav format.
According to the research I did, this format can contain mp3 compression.

In simple words - you can create a wav file with mp3 compression.
Reed more here:

http://en.wikipedia.org/wiki/WAV (Read after "WAV file compression codecs compared" )
 

Windows can play mp3 compression but only if you will add the WAVE-Header (of the wav format).

So my code do exactly this.

my code add the WAVE-Header to the mp3 file and then send it to winmm.dll to play this.

 

The problem is - the Header must to contain the information about the length of the audio and and other information that I I do not know about and how to edit.

i didn't created that Header. so i don't know how to edit that Header.

I created that Header with the free tool "WaveMP3".

 

If i use that Header for another mp3 file with the same compression but not the same length and longer length then windows will play the file but the sound will ends before the time because the length is according to the header.

So i need more brains to help find out how to change the correct parameters in the Header or create better header.

I have attached my work.

I hope that someone will continue from where I stopped.

By the way,
In the rar file in test.mp3 not i speaking.
Do not worry ..
:)

Play mp3 file with win api.rar

Edited by gil900

Share this post


Link to post
Share on other sites
wakillon

why not use SoundPlay Function ?


AutoIt 3.3.14.2 X86 - SciTE 3.6.0WIN 8.1 X64 - Other Example Scripts

Share this post


Link to post
Share on other sites
gil900

why not use SoundPlay Function ?

You must use sndPlaySound because you create virtual  wav file with mp3 compression in the memory

and the only way i know to play sound from memory is to send the binary data to sndPlaySound .

I don;t know if SoundPlay can play directly from binary string

 

EDIT:

It is designed for the case that no mp3 codec installed on Windows.

You playing mp3 file without any mp3 encoder

And in this case you can play mp3 file directly from the exe (you only need make few changes in the code).

Edited by gil900

Share this post


Link to post
Share on other sites
joakim

Guess both functions will do as they both support SND_MEMORY.

Apparently some calculation is needed in order to get at time length;

http://www.datavoyage.com/mpgscript/mpeghdr.htm

http://www.multiweb.cz/twoinches/mp3inside.htm

Each frame has a constant time length. But in order to get at number of frames, you must parse the entire file. Frames can vary in size with vbr.

And to complicate further, there may exist metadata containers, like id3..

Share this post


Link to post
Share on other sites
Melba23

joakim,

 

Each frame has a constant time length. But in order to get at number of frames, you must parse the entire file. Frames can vary in size with vbr. And to complicate further, there may exist metadata containers, like id3

That is not entirely true - you might find this thread of interest - it was my first contribution to AutoIt. :)

And you can see the final code in the _SoundOpen function of Sound.au3. ;)

M23

Edited by Melba23
Typo

Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind._______My UDFs:

Spoiler

ArrayMultiColSort ---- Sort arrays on multiple columns
ChooseFileFolder ---- Single and multiple selections from specified path treeview listing
Date_Time_Convert -- Easily convert date/time formats, including the language used
ExtMsgBox --------- A highly customisable replacement for MsgBox
GUIExtender -------- Extend and retract multiple sections within a GUI
GUIFrame ---------- Subdivide GUIs into many adjustable frames
GUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView items
GUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeView
Marquee ----------- Scrolling tickertape GUIs
NoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxes
Notify ------------- Small notifications on the edge of the display
Scrollbars ----------Automatically sized scrollbars with a single command
StringSize ---------- Automatically size controls to fit text
Toast -------------- Small GUIs which pop out of the notification area

 

Share this post


Link to post
Share on other sites
gil900

 

The problem is - the Header must to contain the information about the length of the audio and and other information that  I do not know about and how to edit.

 

If i use that Header for another mp3 file with the same compression but not the same length and longer length then windows will play the file but the sound will ends before the time because the length is according to the header.

 

If someone can solve that problem and make code that generate the correct header for the mp3 file then we can create a function which made a MAGIC :sorcerer: - function that can play mp3 file without any codec or dll. :)

this is magic.

 

Guess both functions will do as they both support SND_MEMORY.

Apparently some calculation is needed in order to get at time length;

http://www.datavoyage.com/mpgscript/mpeghdr.htm

http://www.multiweb.cz/twoinches/mp3inside.htm

Each frame has a constant time length. But in order to get at number of frames, you must parse the entire file. Frames can vary in size with vbr.

And to complicate further, there may exist metadata containers, like id3..

 

If you learning how to to calculate the time and how to generate the header then good luck..

after you know how to do this, you can write code that makes the header and  that's exactly what is needed!

 

Share this post


Link to post
Share on other sites
joakim

joakim,

 

That is not entirely true - you might find this thread of interest - it was my first contribution to AutoIt. :)

And you can see the final code in the _SoundOpen function of Sound.au3. ;)

M23

That's really nice! I did not notice it before now. :)

One question, since I may not have understood all the code yet: Is it possible to use this code and calculate mp3 sound length for a resource or some memory chunk (without temporarily writing it to disk as .mp3)?

My quick assumption is no, since it is based on reading file properties. Or yes if temporarily writing the data to disk as .mp3 is an option..

Share this post


Link to post
Share on other sites
Melba23

joakim,

Although the default method used by the UDF is to look at the file properties (because it is the easiest to do ;)) most of the code uses secondary methods to look inside the mp3 frames. The Xing header is the real key for VBR files with MCI as a less accurate backup. :)

Take a look at the code in detail and do not hesitate to come back with questions if necessary - although it was a long time ago I think I can just about remember what went on. :D

M23


Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind._______My UDFs:

Spoiler

ArrayMultiColSort ---- Sort arrays on multiple columns
ChooseFileFolder ---- Single and multiple selections from specified path treeview listing
Date_Time_Convert -- Easily convert date/time formats, including the language used
ExtMsgBox --------- A highly customisable replacement for MsgBox
GUIExtender -------- Extend and retract multiple sections within a GUI
GUIFrame ---------- Subdivide GUIs into many adjustable frames
GUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView items
GUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeView
Marquee ----------- Scrolling tickertape GUIs
NoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxes
Notify ------------- Small notifications on the edge of the display
Scrollbars ----------Automatically sized scrollbars with a single command
StringSize ---------- Automatically size controls to fit text
Toast -------------- Small GUIs which pop out of the notification area

 

Share this post


Link to post
Share on other sites
joakim

So I think I understand a bit more it now :)

However, still I am not sure if MCI/mciSendString is supposed to support a memory pointer as lpszDevice (instead of filename as in sound.au3). Curently I get error code 263:  "The specified device is not open or is not recognized by MCI."

And still I am not sure exactly how mciSendString in the end resolves audio length with lpszCommand=status and lpszOpenFlags=length. Do you know Melba23?

Share this post


Link to post
Share on other sites
Melba23

joakim,

Re-reading the thread to which I linked, I see that you do not need to use MCI at all - the final post shows how to get a basic CBR time from the header. So I suggest that in the case of a resource file you first look at getting the CBR length (which will only be correct for a CBR encoded file) and then look for a Xing header - if you find one then it is a VBR file and you need to parse that header to get the length; if not then it is CBR and the initial timing will be correct. :)

I shall look into this more deeply tomorrow when I have dug out all the mp3 documentation that I used when developing the Sound UDF - I think I know where it is on my hard drive. :D

M23


Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind._______My UDFs:

Spoiler

ArrayMultiColSort ---- Sort arrays on multiple columns
ChooseFileFolder ---- Single and multiple selections from specified path treeview listing
Date_Time_Convert -- Easily convert date/time formats, including the language used
ExtMsgBox --------- A highly customisable replacement for MsgBox
GUIExtender -------- Extend and retract multiple sections within a GUI
GUIFrame ---------- Subdivide GUIs into many adjustable frames
GUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView items
GUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeView
Marquee ----------- Scrolling tickertape GUIs
NoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxes
Notify ------------- Small notifications on the edge of the display
Scrollbars ----------Automatically sized scrollbars with a single command
StringSize ---------- Automatically size controls to fit text
Toast -------------- Small GUIs which pop out of the notification area

 

Share this post


Link to post
Share on other sites
gil900

joakim, If I understood correctly:
Your goal is to create a code / use code which calculates the length of the mp3 file and then create / use code which
generates the wave header with the length.

I understood correctly?

If so then how are you going to write the length of the mp3 file in wav header?

It seems like you know how to do it ..
I looked at the header and i do not see anything readable that you can easily edit.

this is how the header that my script add look:

29f80nd.png

i can't see(in the red area) Where the length is written. i know that the length is written in that area because I did some checks ..

Edited by gil900

Share this post


Link to post
Share on other sites
Melba23

gil900,

You cannot save the timing in a wav header - there is nowhere to put it. What you will have to do is get the timing from the mp3 section of the resource before you play it. :)

Anyway I have been having fun today with this! :D

Here is my version of a script to convert an mp3 file to a hybrid wav fileL

; Basic wav header blocks
$sHdr_1 = "0x52494646"
$sHdr_2 = "57415645666D74201E0000005500020044AC0000581B0000010000000C00010002000000B600010071056661637404000000640E060064617461"
$sAlign_Buffer = "00"

; Select mp3 file
$sFile = FileOpenDialog("Select mp3 file to convert", @ScriptDir, "mp3 (*.mp3)")
If Not $sFile Then Exit

; Read mp3 file
$sMp3 = StringTrimLeft(Binary(FileRead($sFile)), 2)
$iMp3Size = StringLen($sMp3)

; Get file size
$iFileSize = FileGetSize($sFile)
; Convert to required format
$iMp3Size = StringRegExpReplace(Hex($iFileSize, 8), "(..)(..)(..)(..)", "$4$3$2$1")
$iWavSize = StringRegExpReplace(Hex($iFileSize + 63, 8), "(..)(..)(..)(..)", "$4$3$2$1")

; Construct hybrid wav file
$sHybridWav = $sHdr_1 & $iWavSize & $sHdr_2 & $iMp3Size & $sMp3
If Mod($iMp3Size, 2) Then
    $sHybridWav &= $sAlign_Buffer
EndIf

; Save new file
$sFile = FileSaveDialog("Select filename for new file", @ScriptDir, "wav (*.wav)")
If Not $sFile Then Exit
$hFile = FileOpen($sFile, 2 + 16)
FileWrite($hFile, $sHybridWav)
FileClose($hFile)
It will take any mp3 file - even one with an ID3 tag - and convert it to a hybrid wav file by adding a valid wav header (and padding the file by one byte if required). A valid wav header contains sizing data and so needs to be calculated for each file - you cannot just add the same string to every mp3. ;)

And then I developed this script to play the hybrid wav file from the resource table - reading the timing from the file itself:

#AutoIt3Wrapper_Res_File_Add=Sound.wav, RT_RCDATA, SOUND

#include <Date.au3>

#include <Resources.au3>

_Play_Resource_Sound("SOUND")

Func _Play_Resource_Sound($sName)

    ; Read resource into memory
    $pResPointer = _ResourceGet($sName)
    $iResSize = @extended
    $tResStruct = DllStructCreate("byte[" & $iResSize & "]", $pResPointer)
    $sSound = DllStructGetData($tResStruct, 1)

    ; Calculate CBR timing
    $iTrackLen_CBR = _CBR_Timing(StringLeft($sSound, 5120), $iResSize)
    $iBitrate = @extended

    ; Look for VBR timing
    $iTrackLen_VBR = _VBR_Timing(StringLeft($sSound, 5120))

    ; Play sound
    Local $SND_NODEFAULT = 2
    Local $iFlag = BitOR($SND_MEMORY, $SND_ASYNC, $SND_NODEFAULT)
    DllCall("winmm.dll", "int", "sndPlaySound", "ptr", $pResPointer, "UINT", $iFlag)

    ; Show results
    If $iTrackLen_VBR = 0 Then
        MsgBox(0, "CBR", _Convert_Timing($iTrackLen_CBR) & @CRLF & $iBitrate & " - " & Hex($iBitrate * 100, 8))
    Else
        MsgBox(0, "VBR", _Convert_Timing($iTrackLen_VBR) & @CRLF & $iBitrate & " - " & Hex($iBitrate * 100, 8))
    EndIf

EndFunc   ;==>_Play_Resource_Sound

Func _Convert_Timing($iMs)
    Local $iHours, $iMins, $iSecs
    _TicksToTime($iMs, $iHours, $iMins, $iSecs)
    Return StringFormat("%02i:%02i:%02i", $iHours, $iMins, $iSecs)
EndFunc   ;==>_Convert_Timing

Func _CBR_Timing($sSound, $iResSize)

    ; Look for start of MP3 header
    Local $iMP3_Start = StringInStr($sSound, "FFF")
    If Not $iMP3_Start Then
        Return SetError(1, 0, 0)
    EndIf

    Local $sSound_Header = StringMid($sSound, $iMP3_Start, 8)

    ; Create look up table (this is only filled for MPEG-1 layer III)
    Local $aBit_Table[14] = [32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]
    ; Look up bitrate
    $iBitrate = $aBit_Table[Number("0x" & StringMid($sSound_Header, 5, 1)) - 1]

    ; Length in ms
    Return SetExtended($iBitrate, Int($iResSize * 8 / $iBitrate))

EndFunc   ;==>_CBR_Timing

Func _VBR_Timing($sTag)

    Local $iXingPos = StringInStr($sTag, "58696E67") ; Xing
    If Not $iXingPos Then
        Return SetError(1, 0, 0)
    EndIf

    ; Read fields flag
    Local $iFrames, $iFlags = Number("0x" & StringMid($sTag, $iXingPos + 14, 2))
    If BitAND($iFlags, 1) = 1 Then
        $iFrames = Number("0x" & StringMid($sTag, $iXingPos + 16, 8))
    Else
        Return SetError(1, 0, 0); No frames field
    EndIf

    ; Now to find Samples per frame & Sampling rate
    ; Go back to the frame header start
    Local $sHeader = StringMid($sTag, $iXingPos - 72, 8)

    ; Read the relevant bytes
    Local $iMPEGByte = Number("0x" & StringMid($sHeader, 4, 1))
    Local $iFreqByte = Number("0x" & StringMid($sHeader, 6, 1))

    ; Decode them
    ; 8 = MPEG-1, 0 = MPEG-2
    Local $iMPEGVer = BitAND($iMPEGByte, 8)

    ; 2 = Layer III, 4 = Layer II, 6 = Layer I
    Local $iLayerNum = BitAND($iMPEGByte, 6)

    Local $iSamples
    Switch $iLayerNum
        Case 6
            $iSamples = 384
        Case 4
            $iSamples = 1152
        Case 2
            Switch $iMPEGVer
                Case 8
                    $iSamples = 1152
                Case 0
                    $iSamples = 576
                Case Else
                    $iSamples = 0
            EndSwitch
        Case Else
            $iSamples = 0
    EndSwitch

    ; If not valid return
    If $iSamples = 0 Then Return SetError(1, 0, 0)

    ; 0 = bit 00, 4 = Bit 01, 8 = Bit 10
    Local $iFrequency, $iFreqNum = BitAND($iFreqByte, 12)
    Switch $iFreqNum
        Case 0
            $iFrequency = 44100
        Case 4
            $iFrequency = 48000
        Case 8
            $iFrequency = 32000
        Case Else
            $iFrequency = 0
    EndSwitch

    ; If not valid return
    If $iFrequency = 0 Then Return SetError(1, 0, 0)

    ; MPEG-2 halves the value
    If $iMPEGVer = 0 Then $iFrequency = $iFrequency / 2

    ; Duration in ms = No of frames * Samples per frame / Sampling freq * 1000
    Return Int(($iFrames * $iSamples / $iFrequency) * 1000)

EndFunc   ;==>_VBR_Timing
Do not cancel the MsgBox if you want to verify the duration as the sound will stop immediately! ;)

Looking forward to some feedback. :)

M23

  • Like 3

Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind._______My UDFs:

Spoiler

ArrayMultiColSort ---- Sort arrays on multiple columns
ChooseFileFolder ---- Single and multiple selections from specified path treeview listing
Date_Time_Convert -- Easily convert date/time formats, including the language used
ExtMsgBox --------- A highly customisable replacement for MsgBox
GUIExtender -------- Extend and retract multiple sections within a GUI
GUIFrame ---------- Subdivide GUIs into many adjustable frames
GUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView items
GUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeView
Marquee ----------- Scrolling tickertape GUIs
NoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxes
Notify ------------- Small notifications on the edge of the display
Scrollbars ----------Automatically sized scrollbars with a single command
StringSize ---------- Automatically size controls to fit text
Toast -------------- Small GUIs which pop out of the notification area

 

Share this post


Link to post
Share on other sites
gil900

Thank you Melba23!

I'm glad someone actually did something useful that works and is based on/result of my discovery/start point.
This is what I expected ..

 

It will be very useful!

Edited by gil900

Share this post


Link to post
Share on other sites
joakim

Looks cool. Will be away for a few days, but will look further into it when back.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×