0
0

Morning

I’m trying to display the picture associated with the MP3 APIC tag. I can get FMOD to access the tag without a problem and via the debugger I can see that FMOD is returning data. The problem is I have no idea how to access this data. I imagine that I would access it through a MemoryStream and use that to populate the picturebox object. Does anyone have any code they wish to share that details how to process the data returned from this tag.

Next, I have a list of genres from [url:29qe24fq]http://www.id3.org/id3v2.4.0-frames[/url:29qe24fq] but I don’t seam to be able to get the right genre to display. E.g. I have a MP3 that shows up as ‘Rock/POP’ in every other music player but in FMOD it shows up as 12, which corresponds to ‘Other’ in the list provided by the link above. Any ideas?

Finally, If I ask FMOD to return the ARTIST tag will it return the id3v2 first of the id3v1 tag? If I load a WMA file will asking for the ARTIST tag return the correct data or will I have to ask for the specific WMA tag? Basically I’m trying to find out how intelligent FMOD is, and how much work I need to do to get it to work the way I want it to.

Thanks.

  • You must to post comments
0
0

[quote="nci":2a9dsgxk]I could easily dump the data to a temp file, but I want to avoid that.
Is there anyway to read it from memory in plain old C? I would like to be able to read the pic data that way.
thanks[/quote:2a9dsgxk]

Is this any help? Its in VB6 but if its what you want it should be convertible.

[url:2a9dsgxk]http://www.xtremevbtalk.com/showthread.php?threadid=46292[/url:2a9dsgxk]

  • You must to post comments
0
0

I’m surprised that nobody has ever done anything like what I’m trying to do.

Anyway, I’ve made a bit of progress with the problem of APIC.

[code:277yh2nl]FMOD.TAG tag = new FMOD.TAG();
if (sound.getTag("APIC", 0, ref tag) == FMOD.RESULT.OK)
{
byte[] byteAryPic = new byte[(int)tag.datalen];
Marshal.Copy(tag.data, byteAryPic, 0, (int)tag.datalen);

Stream strPicData = new MemoryStream(byteAryPic);

bitmapPic = new Bitmap(strPicData);

bitmapPic.Save(@"C:\bob.bmp");

}[/code:277yh2nl]

but at the [code:277yh2nl]bitmapPic = new Bitmap(strPicData);[/code:277yh2nl] line the following exception is generated.

[code:277yh2nl]System.ArgumentException was unhandled
Message="Parameter is not valid."
Source="System.Drawing"[/code:277yh2nl]

Anyone have any idea why this exception is generated and how to solve it?

Notes: The file I’m loading has some album art in it and the byte array is populated with data, its just getting that data into the bitmap structure that’s a problem.

  • You must to post comments
0
0

Thanks, it looks a bit intimidating, but I’ll digest the code when I get home later, thanks for your help :)

  • You must to post comments
0
0

Just for reference I solved the MP3 APIC tag problem a couple of weeks ago during the Easter break.

The code is below for anyone that’s interested. I’ve no idea what will happen if there is multiple images in an MP3 file as I have not been able to test this scenario.

[code:1fpe4djo] //Get the album art
FMOD.TAG tag = new FMOD.TAG();
if (sound.getTag("APIC", 0, ref tag) == FMOD.RESULT.OK)
{
byte[] byteAryPic = new byte[(int)tag.datalen];
Marshal.Copy(tag.data, byteAryPic, 0, (int)tag.datalen);

MemoryStream memStream = new MemoryStream();
BinaryWriter binWriter = new BinaryWriter(memStream);

//Write the data
for (int i = 13; i < byteAryPic.Length; i++)
{
    binWriter.Write(byteAryPic[i]);
}

bitmapPic = new Bitmap(memStream);

binWriter.Close();
memStream.Close();

}
else
{
//Check to see if there is any art in the dir
if (File.Exists(strDir + @"\folder.jpg"))
bitmapPic = new Bitmap(strDir + @"\folder.jpg");
}[/code:1fpe4djo]

I really am slightly shocked that nobody, not even the developers appear to have done anything with WMA tags and genre tags even though FMOD appears to have functions to facilitate their use. Any chance of the documentation being updated to give better examples of there usage for people like me who don’t know the in and outs of the particular file formats?

  • You must to post comments
0
0

For anyone that’s interested I now have the C++ code for reading the APIC data into a HBITMAP structure. The example given below actually goes one step further and loads the HBIRMAP as a OpenGL texture although its easy enough to remove that step if you don’t require it. Some of you may well recognise some of the OpenGL code from the NeHe tutorials.

Note: There is limited error checking in the below code

[code:3ns9iwhk]
//Get the album art
if (sound->getTag("APIC", 0, &tagAlbumnArt) == FMOD_RESULT::FMOD_OK)
{
byte* pbAlbumArt = new byte[tagAlbumnArt.datalen – 13];

        for (int i = 13; i < tagAlbumnArt.datalen; i++)
        {
            pbAlbumArt[i-13] = ((BYTE*)tagAlbumnArt.data)[i];
        }

        //Memory used by pvSourceData/pbAlbumArt is freed after texture is loaded
        pvSourceData = (LPVOID)pbAlbumArt;
        SourceDataLength = tagAlbumnArt.datalen-13;
    }
    else
        MessageBox(NULL, L"No APIC found", NULL, NULL);

[/code:3ns9iwhk]

After getting the data I use this function to read it into a IPicture structure then the HBITMAP structure before finally loading it as a OpenGL texture.

[code:3ns9iwhk]
int CApp::LoadPictureFileI(DWORD dwBuffSize, LPVOID pvSourceData, GLuint &texid)
{
//Get the image from a byte array into a IPicture structure

if (dwBuffSize <= 0)
    return FALSE;

LPVOID pvData = NULL;

//Alloc memory based on buffer size
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwBuffSize);
if (hGlobal == NULL)
    return FALSE;

pvData = GlobalLock(hGlobal);
if (pvData == NULL)
    return FALSE;

//Copy buffer and store in global memory
CopyMemory(pvData, pvSourceData, dwBuffSize);
GlobalUnlock(hGlobal);

LPSTREAM pstm = NULL;

//Create IStream* from global memory
HRESULT hr = CreateStreamOnHGlobal(hGlobal, TRUE, &pstm);
if (FAILED(hr) && pstm == NULL)
    return FALSE;

IPicture *pPicture = NULL;

hr = OleLoadPicture(pstm, dwBuffSize, FALSE, IID_IPicture, (LPVOID*)&pPicture);
if (FAILED(hr) && pPicture == NULL)
    return FALSE;

pstm->Release();
pstm = NULL;

GlobalFree(hGlobal);


//Load the image


HDC         hdcTemp;                            //The DC To Hold Our Bitmap
HBITMAP     hbmpTemp;                           //Holds The Bitmap Temporarily
long        lWidth;                             //Width In Logical Units
long        lHeight;                            //Height In Logical Units
long        lWidthPixels;                       //Width In Pixels
long        lHeightPixels;                      //Height In Pixels
GLint       glMaxTexDim ;                       //Holds Maximum Texture Size

hdcTemp = CreateCompatibleDC(GetDC(0));         //Create The Windows Compatible Device Context
if (!hdcTemp)
{
    pPicture->Release();                     //Decrements IPicture Reference Count
    return FALSE;
}

glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glMaxTexDim);                   //Get Maximum Texture Size Supported

pPicture->get_Width(&lWidth);                                        //Get IPicture Width (Convert To Pixels)
lWidthPixels = MulDiv(lWidth, GetDeviceCaps(hdcTemp, LOGPIXELSX), 2540);
pPicture->get_Height(&lHeight);                                      //Get IPicture Height (Convert To Pixels)
lHeightPixels = MulDiv(lHeight, GetDeviceCaps(hdcTemp, LOGPIXELSY), 2540);

//Resize Image To Closest Power Of Two
if (lWidthPixels <= glMaxTexDim)     //Is Image Width Less Than Or Equal To Cards Limit
    lWidthPixels = 1 << (int)floor((log((double)lWidthPixels)/log(2.0f)) + 0.5f); 
else                                    //Otherwise  Set Width To "Max Power Of Two" That The Card Can Handle
    lWidthPixels = glMaxTexDim;

if (lHeightPixels <= glMaxTexDim)        //Is Image Height Greater Than Cards Limit
    lHeightPixels = 1 << (int)floor((log((double)lHeightPixels)/log(2.0f)) + 0.5f);
else                                    //Otherwise  Set Height To "Max Power Of Two" That The Card Can Handle
    lHeightPixels = glMaxTexDim;

//Create A Temporary Bitmap
BITMAPINFO bi = {0};                                          //The Type Of Bitmap We Request
DWORD *pBits = 0;                                               //Pointer To The Bitmap Bits

bi.bmiHeader.biSize         = sizeof(BITMAPINFOHEADER);             // Set Structure Size
bi.bmiHeader.biBitCount     = 32;                                   // 32 Bit
bi.bmiHeader.biWidth        = lWidthPixels;                         // Power Of Two Width
bi.bmiHeader.biHeight       = lHeightPixels;                        // Make Image Top Up (Positive Y-Axis)
bi.bmiHeader.biCompression  = BI_RGB;                               // RGB Encoding
bi.bmiHeader.biPlanes       = 1;                                    // 1 Bitplane

//Creating A Bitmap This Way Allows Us To Specify Color Depth And Gives Us Imediate Access To The Bits
hbmpTemp = CreateDIBSection(hdcTemp, &bi, DIB_RGB_COLORS, (void**)&pBits, 0, 0);

if(!hbmpTemp)                                                       // Did Creation Fail?
{
    DeleteDC(hdcTemp);                                              // Delete The Device Context
    pPicture->Release();                                         // Decrements IPicture Reference Count
    return FALSE;                                                   // Return False (Failure)
}

SelectObject(hdcTemp, hbmpTemp);                                    // Select Handle To Our Temp DC And Our Temp Bitmap Object

// Render The IPicture On To The Bitmap
pPicture->Render(hdcTemp, 0, 0, lWidthPixels, lHeightPixels, 0, lHeight, lWidth, -lHeight, 0);

// Convert From BGR To RGB Format And Add An Alpha Value Of 255
for(long i = 0; i < lWidthPixels * lHeightPixels; i++)               // Loop Through All Of The Pixels
{
    BYTE* pPixel    = (BYTE*)(&pBits[i]);                           // Grab The Current Pixel
    BYTE  temp      = pPixel[0];                                    // Store 1st Color In Temp Variable (Blue)
    pPixel[0]       = pPixel[2];                                    // Move Red Value To Correct Position (1st)
    pPixel[2]       = temp;                                         // Move Temp Value To Correct Blue Position (3rd)

    // This Will Make Any Black Pixels, Completely Transparent      (You Can Hardcode The Value If You Wish)
    if ((pPixel[0]==0) && (pPixel[1]==0) && (pPixel[2]==0))         // Is Pixel Completely Black
        pPixel[3]   =   0;                                          // Set The Alpha Value To 0
    else                                                            // Otherwise
        pPixel[3]   = 255;                                          // Set The Alpha Value To 255
}

glGenTextures(1, &texid);                                           // Create The Texture

// Typical Texture Generation Using Data From The Bitmap
glBindTexture(GL_TEXTURE_2D, texid);                                // Bind To The Texture ID
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);     // (Modify This For The Type Of Filtering You Want)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);     // (Modify This For The Type Of Filtering You Want)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, lWidthPixels, lHeightPixels, 0, GL_RGBA, GL_UNSIGNED_BYTE, pBits);  // (Modify This If You Want Mipmaps)

DeleteObject(hbmpTemp);                                             // Delete The Object
DeleteDC(hdcTemp);                                                  // Delete The Device Context

pPicture->Release();                                             // Decrements IPicture Reference Count

return TRUE;                                                        // Return True (All Good)

}
[/code:3ns9iwhk]

After all of that you need to remember to free the memory used by pvSourceData in the first section of code

[code:3ns9iwhk]
Free(pvSourceData);
[/code:3ns9iwhk]

Hope this will be of use to someone.

  • You must to post comments
0
0

About your Genre problem, I found this from ID3.org

[quote:d3thu0c8]References to the ID3v1 genres can be made by, as first byte, enter
"(" followed by a number from the genres list (appendix A.) and
ended with a ")" character. This is optionally followed by a
refinement, e.g. "(21)" or "(4)Eurodisco". Several references can be
made in the same frame, e.g. "(51)(39)". If the refinement should
begin with a "(" character it should be replaced with "((", e.g. "((I
can figure out any genre)" or "(55)((I think…)". The following new
content types is defined in ID3v2 and is implemented in the same way
as the numerig content types, e.g. "(RX)".

 RX  Remix
 CR  Cover

[/quote:d3thu0c8]

Do you have a hex editor? If not I recommend Hexplorer. You can look inside the file and see exactly what is going on with the TCON frame. Maybe FMOD only gets the first genre number? Perhaps for some stupid reason it was tagged like
TCON (12)Rock/POP
or something.

  • You must to post comments
0
0

All,

maybe this helps here as well. My finding is that FMOD simply returns the raw tag frame content but does not interpret them. The only exception being the frame length which it does interpret.

According to the ID3v2.4 Spec the APIC frame is defined as:
[code:34ctafot]<Header for ‘Attached picture’, ID: "APIC">
Text encoding $xx
MIME type <text string> $00
Picture type $xx
Description <text string according to encoding> $00 (00)
Picture data <binary data>[/code:34ctafot]
If you query FMOD for the APIC frame you will get everything starring with the Text encoding.

Similar for the ID3v2.0 PIC frame which is define as:
[code:34ctafot]Attached picture "PIC"
Frame size $xx xx xx
Text encoding $xx
Image format $xx xx xx
Picture type $xx
Description <textstring> $00 (00)
Picture data <binary data>[/code:34ctafot]
In this case FMOD processes the frame size which will give you everything starting with the text encoding (again).

If you want the raw pic bytes you have to skip the (variable length) header.

So if you want to interpret ID3 Tags you should always take a look at the matching specification to interpret them – even if the data "just seems to be a string". Sometimes even strings have a deeper meaning.

This also means that you might have to check for different frames to get the same data (e.g. "Lead Artist" is "TP1" in ID3v2.0 which corresponds to "TPE1" being "Lead Performer" in ID3v2.4).

The ID3 Spec docs are your friend: :-)
http://www.id3.org/Developer_Information

So always check how to interpret the content of the frames.

CU,

Udo

  • You must to post comments
0
0

Thanks, I’ll give that a look.

  • You must to post comments
0
0

[quote="bobbobber23":2bapjao5]Just for reference I solved the MP3 APIC tag problem a couple of weeks ago during the Easter break.

The code is below for anyone that’s interested. I’ve no idea what will happen if there is multiple images in an MP3 file as I have not been able to test this scenario.

[code:2bapjao5] //Get the album art
FMOD.TAG tag = new FMOD.TAG();
if (sound.getTag("APIC", 0, ref tag) == FMOD.RESULT.OK)
{
byte[] byteAryPic = new byte[(int)tag.datalen];
Marshal.Copy(tag.data, byteAryPic, 0, (int)tag.datalen);

MemoryStream memStream = new MemoryStream();
BinaryWriter binWriter = new BinaryWriter(memStream);

//Write the data
for (int i = 13; i &lt; byteAryPic.Length; i++)
{
    binWriter.Write(byteAryPic[i]);
}

bitmapPic = new Bitmap(memStream);

binWriter.Close();
memStream.Close();

}
else
{
//Check to see if there is any art in the dir
if (File.Exists(strDir + @"\folder.jpg"))
bitmapPic = new Bitmap(strDir + @"\folder.jpg");
}[/code:2bapjao5]

I really am slightly shocked that nobody, not even the developers appear to have done anything with WMA tags and genre tags even though FMOD appears to have functions to facilitate their use. Any chance of the documentation being updated to give better examples of there usage for people like me who don’t know the in and outs of the particular file formats?[/quote:2bapjao5]

hi all,

how can i load the binary data in DELPHI (5 or 7)???

i don’t know how do i load the data in a TMemoryStream.

thank’s for answers!

  • You must to post comments
0
0

[quote="punker76":3kknlepg]
hi all,

how can i load the binary data in DELPHI (5 or 7)???

i don’t know how do i load the data in a TMemoryStream.

thank’s for answers![/quote:3kknlepg]

Not a clue, but you might be able to save the data to a temp file, load the contents of the file and then delete the file. If you could get the TMemoryStream thing working I would recommend that as I would imagine its more efficient and you don’t have to worry about temp files, permissions etc.

  • You must to post comments
0
0

[quote="bobbobber23":1p4m9afu]Just for reference I solved the MP3 APIC tag problem a couple of weeks ago during the Easter break.

The code is below for anyone that’s interested. I’ve no idea what will happen if there is multiple images in an MP3 file as I have not been able to test this scenario.

[code:1p4m9afu] //Get the album art
FMOD.TAG tag = new FMOD.TAG();
if (sound.getTag("APIC", 0, ref tag) == FMOD.RESULT.OK)
{
byte[] byteAryPic = new byte[(int)tag.datalen];
Marshal.Copy(tag.data, byteAryPic, 0, (int)tag.datalen);

MemoryStream memStream = new MemoryStream();
BinaryWriter binWriter = new BinaryWriter(memStream);

//Write the data
for (int i = 13; i &lt; byteAryPic.Length; i++)
{
    binWriter.Write(byteAryPic[i]);
}

bitmapPic = new Bitmap(memStream);

binWriter.Close();
memStream.Close();

}
else
{
//Check to see if there is any art in the dir
if (File.Exists(strDir + @"\folder.jpg"))
bitmapPic = new Bitmap(strDir + @"\folder.jpg");
}[/code:1p4m9afu]

I really am slightly shocked that nobody, not even the developers appear to have done anything with WMA tags and genre tags even though FMOD appears to have functions to facilitate their use. Any chance of the documentation being updated to give better examples of there usage for people like me who don’t know the in and outs of the particular file formats?[/quote:1p4m9afu]

I have got excatly the same problem of getting the image from the APIC tag.

However I am getting an error with the posted code above :

System.ArgumentException: Parameter is not valid.
at System.Drawing.Bitmap..ctor(Stream stream)
at Core.Sound.Tags.ReadCover() in C:\Dokumente und Einstellungen..\Tags.cs:line 240
A first chance exception of type ‘System.ArgumentException’ occurred in System.Drawing.dll

Seams to be that the constructor does not fit:

bitmapPic = new Bitmap(memStream);

Any ideas how to solve this problem?

  • You must to post comments
0
0

[quote:19f88bil]Seams to be that the constructor does not fit:

bitmapPic = new Bitmap(memStream);

Any ideas how to solve this problem?[/quote:19f88bil]

I got this error when non-bitmap data was in the memory stream. Try tweaking the initial value of i in the for loop and see what happens. It may be a bit of trial and error is needed.

As far as I’m aware this should not be needed though as the first part of the byte array that we ignore should always be the same length as the ID3V2 spec specifies it as so. Might be wrong though.

  • You must to post comments
0
0

[quote="bobbobber23":odv1aqaq][quote="punker76":odv1aqaq]
hi all,

how can i load the binary data in DELPHI (5 or 7)???

i don’t know how do i load the data in a TMemoryStream.

thank’s for answers![/quote:odv1aqaq]

Not a clue, but you might be able to save the data to a temp file, load the contents of the file and then delete the file. If you could get the TMemoryStream thing working I would recommend that as I would imagine its more efficient and you don’t have to worry about temp files, permissions etc.[/quote:odv1aqaq]

hi all delphi users,

this code works fine for me:

[code:odv1aqaq]
type
TByteArray = array of Byte;
var
fmodtag: FMOD_TAG;
xMemStream: TMemoryStream;
bytes: ^TByteArray;
begin
FMOD_Sound_GetTag(fSound, ‘APIC’, 0, fmodtag);
xMemStream:= TMemoryStream.Create();
try
bytes:= fmodtag.data;
xMemStream.Write(bytes^, fmodtag.datalen);
xMemStream.Position:= 0;

// Do something with the stream

...

finally
xMemStream.Free;
end;
end;
[/code:odv1aqaq]

punker76

  • You must to post comments
0
0

I could easily dump the data to a temp file, but I want to avoid that.
Is there anyway to read it from memory in plain old C? I would like to be able to read the pic data that way.
thanks

  • You must to post comments
Showing 14 results
Your Answer

Please first to submit.