![]() |
Multimedia »
Audio and Video »
Audio
Intermediate
License: The Code Project Open License (CPOL)
Using Winamp input plugins for tagging audio filesBy OrlandoCuriosoC# Interop with dynamic loaded plug-ins and function pointers |
C# 2.0, Windows, .NET 2.0VS2005, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||

Several years ago, I struggled with reliably reading the tags of audio files in diverse formats and versions. No existing library managed to cope with the variety of emerging MP3 successors in my vast collection. At last, I settled on using Winamp input plug-ins. The basic idea was to restrict myself to the most basic fields of audio tags, yet manage these reliably for all future tag formats.
I wrote a C++ wrapper DLL based on CDex source that relayed calls from VB6 clients and never used anything else. On the occasion of converting a client application to .NET, I tried a pure C# solution... and failed miserably. Recently, my second attempt amid several access violations proved successful. Regardless of my initial aim, you can probably use the presented class to play/decode audio with Winamp plug-ins. It might also be of interest if you are absolutely clueless about doing Interop with C-style function pointers.
Documentation, info and inspiration:
Winamp input plug-ins provide a reference to a 38-member structure, In_Module, by calling into the exported method, WINAMPGETINMODULE2:
__declspec(dllexport) In_Module* (*WINAMPGETINMODULE2)( void );
After some information fields at the beginning, the In_Module structure consists of function pointers of static plug-in functions. Finally, pointers of Winamp visualisation functions:
typedef struct
{
int version; // module type (IN_VER == 0x100)
char *description; // description of module
HWND hMainWindow; // Winamp's main window (filled in by Winamp)
HINSTANCE hDllInstance; // DLL instance (filled in by Winamp)
char *FileExtensions; // "mp3,mp2,mpg"
// ...
void (*Config)(HWND hwndParent); // configuration dialog
void (*About)(HWND hwndParent); // about dialog
void (*Init)(); // called at program init
void (*Quit)(); // called at program quit
// ...
Out_Module *outMod; // filled in by Winamp
}
In_Module;
Winamp loads the plug-in and calls WINAMPGETINMODULE2. The plug-in allocates a static instance of In_Module. It then fills the struct with preliminary description and extension fields, plus all of its function pointers. Finally, it returns a reference. Winamp sets the hMainWindow and hDllInstance fields, plus all of its function pointers. It then calls the plug-in's Init function. The plug-in initializes based upon its user configuration and updates the description and extensions fields. Until Winamp invokes the plug-in's Quit method and unloads the plug-in, both can now communicate as peers by calling their mutual functions.
In order to load a plug-in from managed C#, we convert the above types into their respective managed counterparts. The unmanaged function pointers are replaced by delegate types with an appropriate signature, plus a MarshalAs attribute. I chose to nest the 9 needed delegates inside the class and to name them after their return/parameter types, i.e. VoidIntPtrdelegate. The fields of function pointers, supposed to be filled in by Winamp, have the IntPtr data type and can be left empty here.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Size = 152)]
internal class In_Module
{
public delegate void VoidIntPtrdelegate(IntPtr hwndParent);
public delegate void VoidVoiddelegate();
// ...
public int version;
public string description;
public IntPtr hMainWindow;
public IntPtr hDllInstance;
public string FileExtensions;
// ...
[MarshalAs(UnmanagedType.FunctionPtr)]
public VoidIntPtrdelegate Config;
[MarshalAs(UnmanagedType.FunctionPtr)]
public VoidIntPtrdelegate About;
[MarshalAs(UnmanagedType.FunctionPtr)]
public VoidVoiddelegate Init;
[MarshalAs(UnmanagedType.FunctionPtr)]
public VoidVoiddelegate Quit;
// ...
public IntPtr outMod;
}
As In_Module contains non-blittable types -- i.e. strings and delegates -- it cannot be pinned. Furthermore, we must marshal data repeatedly between the managed/unmanaged copies. Thus, we declare a delegate for WINAMPGETINMODULE2, which returns a pointer to the unmanaged struct instead of marshalling directly to the managed class.
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal delegate IntPtr GetInModulePtr();
Sanity omitted:
IntPtr hDll = Win32.LoadLibrary("in_mp3.dll");
IntPtr pFunc = Win32.GetProcAddress(hDll, "WinampGetInModule2");
GetInModulePtr delFunc = (GetInModulePtr)
Marshal.GetDelegateForFunctionPointer(pFunc, typeof(GetInModulePtr));
IntPtr pModule = delFunc();
In_Module inModule =
(In_Module)Marshal.PtrToStructure(pModule, typeof(In_Module));
inModule.hMainWindow = this.hWnd;
inModule.hDllInstance = hDll;
Marshal.StructureToPtr(inModule, pModule, false);
inModule.Init();
inModule = (In_Module)Marshal.PtrToStructure(pModule, typeof(In_Module));
// show plug-in's configuration dialog
inModule.Config(this.hWnd);
Debug.Print(inModule.FileExtensions);
inModule.Quit();
Win32.FreeLibrary(hDll);
The repeated copying of data is plainly inelegant, but comes with negligible performance cost.
Beginning with Winamp 5.10, Nullsoft began implementing Unicode capabilities. The Winamp SDK 5.32 Beta defines a Unicode In_Module in the in2.h header. Only few plug-in functions use Unicode parameters, mostly file paths. The description and FileExtensions fields still use ANSI strings. It is therefore possible here to work with a common ANSI In_Module if ambiguous functions use the IntPtr data type. The corresponding delegates are now converted with GetDelegateForFunctionPointer as needed, rather than by attributing the fields.
Winamp SDK defines functions dealing with tags -- called ExtendedFileInfo -- in the wa_ipc.h header: IPC_GET_EXTENDED_FILE_INFO, IPC_SET_EXTENDED_FILE_INFO, IPC_WRITE_EXTENDED_FILE_INFO. These apply to Winamp itself. The corresponding plug-in methods, however, are not documented. Uncovered by Michael Facquet (see article):
typedef int (*WINAMPGETEXTENDEDFILEINFO) (
char* filename,char* metadata,char* ret,int retlen);
typedef int (*WINAMPSETEXTENDEDFILEINFO) (
char* filename,char* metadata,char* value);
typedef int (*WINAMPWRITEEXTENDEDFILEINFO)();
Newer plug-ins, such as Winamp 5.10+, export Unicode versions of these functions. The metadata parameter (always ANSI) identifies a particular field of the info as string, i.e. "artist," "bitrate." Setting info caches the data in the plug-in. Finally, it must be written to the file. As an example, we'll set the title field using a previously loaded plug-in:
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)]
private delegate int WinampSetExtendedFileInfo(
string filename, string metadata, string value);
IntPtr pFunc = Win32.GetProcAddress(hDll, "WinampSetExtendedFileInfo");
WinampSetExtendedFileInfo delFunc = (WinampSetExtendedFileInfo)
Marshal.GetDelegateForFunctionPointer(
pFunc, typeof(WinampSetExtendedFileInfo));
delFunc(filename, "title", "Whip the Llama's Ass");
The WinampMetadata class encapsulates the needed functionality. Its public interface looks like this:
public class WinampMetadata : IDisposable
{
public WinampMetadata()
public int PluginCount { get; }
public string Extensions { get; }
public bool InitPlugins(DirectoryInfo dir, IntPtr hWndOwner)
public void ClosePlugins()
public bool InitFileInfo(FileInfo file)
public bool GetFileInfo(MetaDataType dataType, out string value)
public bool SetFileInfo(MetaDataType dataType, string value)
public bool WriteFileInfo()
public bool GetFileInfoBatch(out MetaData metaData)
public bool GetFileInfoBatch2(out MetaData2 metaData)
public bool SetFileInfoBatch(MetaData metaData)
public void InfoBox(IntPtr hWnd)
public void ConfigPlugin(IntPtr hWnd)
public void AboutPlugin(IntPtr hWnd)
public void Dispose()
}
InitPlugins enumerates all input plug-ins (in_*.dll) that export the ExtendedFileInfo methods. It assembles a list of plug-in names, the respective file extensions they support and whether they use Unicode. All plug-ins are unloaded immediately. InitFileInfo ensures that an adequate plug-in is loaded for the specified file, either by reusing the last one or by searching in the internal list and loading the corresponding plug-in. The current plug-in is only unloaded if another one is needed or if the class is disposed. Individual tag fields are specified by an enum MetaDataType, rather than strings:
public enum MetaDataType
{
Artist = 0,
Album,
Track,
Title,
Year,
Comment,
Genre,
Length, // length of song in milliseconds
Bitrate, // in kbps if vbr, returns avg bitrate
SampleRate, // sampling frequency
Stereo, // 1 if stereo, 0 otherwise
VBR // if is vbr number of frames, else 0
}
In most cases, you will want to work with complete information. The XXXBatch methods utilize two structures, representing the writable and read-only parts. They allow getting/setting data in one operation.
public struct MetaData
{
public string Artist;
public string Album;
public string Track;
public string Title;
public string Year;
public string Comment;
public string Genre;
public static MetaData Empty = new MetaData();
}
public struct MetaData2
{
public string Length;
public string Bitrate;
public string SampleRate;
public string Stereo;
public string VBR;
}
The InfoBox method shows the built-in tag editor dialog, which may offer more functionality than Winamp's metadata implementation.
The download contains an usercontrol that uses a WinampMetadata class as an engine to manage the tags of a single file. Note how the file's listbox lists only the supported files, returned by the Extensions property. The owner-drawn listbox itself may be a valuable addition to your toolbox, as it employs Carl Daniel's FileSystemEnumerator.
The older ANSI plug-ins were self-contained. The newer versions depend on common (nscrt.dll) and individual (libFLAC, libmp4v2) libraries. Some of these libraries reside in Winamp's installation folder and must be copied to the folder of the executing assembly. This complicates the testing of multiple plug-in versions, as the correct library must be in the EXE folder. You can download previous Winamp installers here, to try older plug-in versions.
Quit function is not called anymore on unloading, as all Unicode plug-ins crash here. No leakage observed.| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 21 Jun 2007 Editor: Genevieve Sovereign |
Copyright 2007 by OrlandoCurioso Everything else Copyright © CodeProject, 1999-2009 Web19 | Advertise on the Code Project |