分享
 
 
 

[Sample] Playing with music file

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

Getting into My Music

If you decide to have your own interface for playing music, one of the fundamental decisions is how to manage your library of MP3/WMA files. Do you use the existing library of another program (such as Microsoft?Windows Media?Player), or do you write your own? This is not a particularly easy decision, and I am still not completely sure which is the best choice, but I had to make a decision or I would never be able to move on in my development. I decided that using a database to create my own library/catalog of music files would be the most flexible option, although this path would likely require more work at the beginning. I set up a new database in MSDE on my development machine, and created the following schema (actually it is a fair bit more complicated, but these are the key tables):

Figure 1. A simple database schema for holding my music library

Of course, after building my own system, along came what is arguably a much better one in the form of Windows XP Media Center Edition, which created a bit of a conflict in my mind. Why continue building my own if I could go get a better one that is completely done for me? The confusion only lasted a few moments though; I am a programmer, so I often build things when it would be much more logical to buy them.

Reading the Tags

Although I didn't have any formal requirements or architectural documentation (maybe it's just me, but having to produce all the same documentation as a work project would take some of the fun out of it), I knew that moving to the next stage would require code that could read all of the attributes from an audio file梐rtist, song, and album names, for example.

I originally assumed my collection would be in MP3 files, which store attributes using various flavors of a system called "ID3," so using the resources available at www.id3.org, I started creating a Microsoft Windows .NET Framework-based library that could handle the more common versions of ID3 tags. In parallel, I began ripping my CD collection onto my hard drive (for my own personal use, of course). For this, I used Windows Media Player, and I spent some time investigating the various options available in terms of WMA (Windows Media Audio) versus MP3 and different encoding rates.

Note Choosing a format and encoding quality requires some research, especially before you rip several hundred CDs to your hard drive. (That is not a task I would like to redo.) I won't spent a lot of time explaining why I chose WMA. I am not really an objective reviewer of audio file formats anyway, but it appears to produce higher quality at lower file sizes, which seems like a good thing to me. Starting with version 9 of Windows Media Player, a variable bit rate (VBR) encoding format is available, which might turn out to be the best choice, but I didn't have access to that format when I was ripping my collection to disk.

I ended up choosing WMA at around 160 kilobits per second (Kbps). Now I needed to make sure my attribute reading code could handle both WMA and MP3 files. Well, to make a long story somewhat shorter, I decided that since Windows Media Player could handle both file formats, I should take a look at the Windows Media SDK. Sure enough, the SDK even provided a sample to do exactly what I wanted. Instead of doing all my work for me and writing the sample as a COM component, however, it was a regular old console application.

I started trying to modify their wonderful C++ code to create a nice friendly component, but it was 3 A.M. and my patience was not up to the challenge. So (in a trend that continues across most of my hobby programming), I cheated and took the quick and dirty way out. I modified their code slightly to handle a larger list of MP3 and WMA tags, and to output the information (to the console still) as XML. With these changes in place, I wrapped the component up into a couple of Framework functions, essentially just calling it with a command-line parameter of the path to my audio file, and retrieving its output back as a nice XML document. Very cheesy, but at 3 A.M. I just wanted to get something working, and this works just fine.

Note The code for the XML generating console application is almost 100 percent identical to the sample in the Windows Media SDK, but the exe is provided for you as part of the code download for this article. I've also included a sample bit of Framework code to call the console application and process its output, so that you can play around with your own MP3 and WMA files.

Using this program from the command line is fairly simple. You just specify the name of the file you want to have it scan. There are no options for multiple files, although that would likely be an excellent addition. Running this program against the WMA file of "Sophia's Pipes" by Ashley MacIssac produces this output to the console:

<Tags>

<Tag><Attribute>Bitrate</Attribute>

<Value>128640</Value></Tag>

<Tag><Attribute>FileSize</Attribute>

<Value>3191530</Value></Tag>

<Tag><Attribute>WM/AlbumTitle</Attribute>

<Value>Hi How Are You Today?</Value></Tag>

<Tag><Attribute>WM/GenreID</Attribute>

<Value>CTRY</Value></Tag>

<Tag><Attribute>Author</Attribute>

<Value>Ashley MacIsaac</Value></Tag>

<Tag><Attribute>WM/Track</Attribute>

<Value>7</Value></Tag>

<Tag><Attribute>Title</Attribute>

<Value>Sophia's Pipes (Walkin' the Floor/Murdo MacKenzie of Torridon)</Value></Tag>

<Tag><Attribute>WM/Year</Attribute>

<Value></Value></Tag>

<Tag><Attribute>WM/MCDI</Attribute>

<Value>D+96+2CDD+8CCF+C1AC+EC6D+14552+1A86F+1F7CF+231D3+26BF1+2C42F+

30E6C+34B02+3B51C</Value></Tag>

<Tag><Attribute>WM/Composer</Attribute>

<Value>Ashley MacIsaac, Pete Prilesnik, trad</Value></Tag>

<Tag><Attribute>WM/Genre</Attribute>

<Value>CTRY</Value></Tag>

<Tag><Attribute>Duration</Attribute>

<Value>1983440000</Value></Tag>

<Tag><Attribute>Is_Protected</Attribute>

<Value>false</Value></Tag>

</Tags>

Although this isn't the prettiest XML, it is valid, and therefore it is easy to read using the System.XML classes in the Framework. Running the console application and grabbing the output is made possible through the System.Diagnostics namespace and the Process and ProcessStartInfo classes.

Public Function RunID3Tags(ByVal filename As String) As String

Dim psi As New ProcessStartInfo()

psi.FileName = ID3Tag_Path

psi.Arguments = String.Format( _

" " & """" & "{0}" & """" & " show", filename)

psi.UseShellExecute = False

psi.RedirectStandardOutput = True

psi.CreateNoWindow = True

Dim p As Process

Dim xmlOutput As String

p = Process.Start(psi)

Try

xmlOutput = p.StandardOutput.ReadToEnd()

p.WaitForExit() ' wait a 1/10th of a second

Return xmlOutput

Finally

' should never happen, but let's play it safe here

If Not p.HasExited Then

p.Kill()

End If

End Try

End Function

Loading the XML

I do a quick little replace to make sure that any "&" characters in the music file tags are interpreted correctly by the XML classes. Then I load this XML in as a new XMLDocument.

Dim sXML As String

sXML = RunID3Tags(fileName)

sXML = sXML.Replace("&", "&amp;")

Dim myXML As New XmlDocument()

myXML.LoadXml(sXML)

Once I have the XMLDocument, I create a new instance of a class (MusicFileInfo) to hold the attributes of the file and fill in the appropriate properties. The complete function that takes a filename and returns an instance of MusicFileInfo is provided below.

Private Function ScanFile(ByVal fileName As String) _

As MusicFileInfo

Dim mfi As New MusicFileInfo()

Dim sXML As String

Try

sXML = RunID3Tags(fileName)

sXML = sXML.Replace("&", "&amp;")

Dim myXML As New XmlDocument()

myXML.LoadXml(sXML)

Dim myNode As XmlNode

Dim sAttribute, sValue As String

With mfi

For Each myNode In _

myXML.GetElementsByTagName("Tag")

sAttribute = myNode.ChildNodes(0).InnerText

sValue = myNode.ChildNodes(1).InnerText

If sValue.Trim() <> String.Empty Then

Select Case sAttribute.ToLower

Case "bitrate"

.Bitrate = CLng(sValue)

Case "wm/albumtitle"

.AlbumTitle = sValue.Trim

Case "wm/genre"

.Genre = sValue.Trim

Case "author"

.Authors = sValue.Trim

Case "wm/track"

.Track = CInt(sValue)

Case "title"

.Title = sValue.Trim

Case "wm/year"

.Year = CInt(sValue)

Case "wm/composer"

.Composers = sValue.Trim

Case "duration"

.Duration = CDec(sValue.Trim)

Case "wm/mcdi"

.TOC = sValue.Trim

End Select

End If

Next

If .AlbumTitle = "" Then

.AlbumTitle = "No Specific Album"

.TOC = ""

End If

'Track is zero based...

'want it to be 1 based,

'to fit in with CD Players

.Track += 1

End With

Catch e As Exception

Debug.Write(e)

Finally

sXML = ""

End Try

Return mfi

End Function

In addition to creating the MusicFileInfo class, I also used the Collection Generator tool from GotDotNet to create a strongly typed collection of MusicFileInfo objects. As I scan in music files, I add their information to this collection and then, because it is a strongly typed collection, I can use it to data bind a grid control.

Public Function ScanFiles(ByVal fileNames() As String) _

As MusicFileInfoCollection

Dim fileName As String

Dim mfis As New MusicFileInfoCollection()

For Each fileName In fileNames

mfis.Add(ScanFile(fileName))

Next

Return mfis

End Function

The final sample application (included in the download for this column) produces the results shown in Figure 2.

Figure 2. The demo MusicFileTags application displays the tags of any WMA/MP3 files you select.

Obsolete Already

It is nice to have code finished and working, but things keep changing in the world of computers, and new options often present themselves just after you have finished your solution. Early this month, January 2003, Microsoft released a new version of Windows Media Player (available from http://www.windowsmedia.com/9series/download/download.asp) and a new version of the Windows Media Format SDK (available from http://msdn.microsoft.com/downloads/list/winmedia.asp). Once I downloaded the new player and the new SDK, I found something wonderful: managed code samples for reading music file attributes. My current solution, reading XML output from a console application, works for me (and in fact I haven't replaced it yet) but the managed code option is much more appealing. The nature of strings, special characters (&, <, >, and so on), and XML means that the original system works for all my current music. Nevertheless, I expect it will eventually find some artist, album, or song name it cannot process successfully. Using a managed-code solution avoids those issues, and also allows for structured error handling (among other benefits).

Included in the SDK's managed code samples is a simple wrapper library, which encapsulates the underlying Windows Media calls in C# code. This wrapper makes it much easier to use those calls from a Microsoft Windows .NET Framework-based application. I have included only the compiled version of that library (in the bin directory of my sample project), but you can get the complete source code by downloading the Format SDK for yourself. Now that all of the Windows Media calls are handled by this wrapper library, the next step is to create a simple class that accepts the file path for a WMA file and retrieves all of the available attributes.

Public Shared Function GetAttributes( _

ByVal filename As String) As MusicFileInfo

Dim mfi As New MusicFileInfo()

Dim editor As IWMMetadataEditor

Dim headerInfo As IWMHeaderInfo3

Dim uHR As UInt32

Dim hr As Int32

uHR = WMFSDKFunctions.WMCreateEditor(editor)

hr = Convert.ToInt32(uHR)

If hr = 0 Then

uHR = editor.Open(filename)

hr = Convert.ToInt32(uHR)

If hr = 0 Then

headerInfo = DirectCast(editor, IWMHeaderInfo3)

Dim value As Byte()

Dim pType As WMT_ATTR_DATATYPE

value = GetAttributeByName(headerInfo, _

"bitrate", pType)

If pType = WMT_ATTR_DATATYPE.WMT_TYPE_DWORD Then

mfi.Bitrate = Convert.ToInt64( _

BitConverter.ToUInt32(value, 0))

End If

value = GetAttributeByName(headerInfo, _

"WM/AlbumTitle", pType)

If pType = WMT_ATTR_DATATYPE.WMT_TYPE_STRING Then

'ConvertToString is a function to convert

'from byte array to String, taking

'unicode encoding into account

mfi.AlbumTitle = ConvertToString(value)

End If

value = GetAttributeByName(headerInfo, _

"WM/Genre", pType)

If pType = WMT_ATTR_DATATYPE.WMT_TYPE_STRING Then

mfi.Genre = ConvertToString(value)

End If

value = GetAttributeByName(headerInfo, _

"Author", pType)

If pType = WMT_ATTR_DATATYPE.WMT_TYPE_STRING Then

mfi.Authors = ConvertToString(value)

End If

value = GetAttributeByName(headerInfo, _

"WM/Track", pType)

If pType = WMT_ATTR_DATATYPE.WMT_TYPE_STRING Then

mfi.Track = CInt(ConvertToString(value))

End If

value = GetAttributeByName(headerInfo, _

"Title", pType)

If pType = WMT_ATTR_DATATYPE.WMT_TYPE_STRING Then

mfi.Title = ConvertToString(value)

End If

value = GetAttributeByName(headerInfo, _

"WM/Year", pType)

If pType = WMT_ATTR_DATATYPE.WMT_TYPE_STRING Then

mfi.Year = CInt(ConvertToString(value))

End If

value = GetAttributeByName(headerInfo, _

"WM/Composer", pType)

If pType = WMT_ATTR_DATATYPE.WMT_TYPE_STRING Then

mfi.Composers = ConvertToString(value)

End If

value = GetAttributeByName(headerInfo, _

"Duration", pType)

If pType = WMT_ATTR_DATATYPE.WMT_TYPE_QWORD Then

mfi.Duration = Convert.ToDecimal( _

BitConverter.ToUInt64(value, 0))

End If

value = GetAttributeByName(headerInfo, _

"WM/MCDI", pType)

If pType = WMT_ATTR_DATATYPE.WMT_TYPE_BINARY Then

mfi.TOC = BitConverter.ToString(value, 0)

End If

End If

End If

Return mfi

End Function

Private Shared Function GetAttributeByName( _

ByVal headerInfo As IWMHeaderInfo3, _

ByVal name As String, _

ByRef pType As WMT_ATTR_DATATYPE) As Byte()

Dim streamNum As UInt16 = Convert.ToUInt16(0)

Dim uHR As UInt32

Dim valueLength As UInt16

Dim arrLength As Int32

Dim hr As Int32

uHR = headerInfo.GetAttributeByName( _

streamNum, name, pType, Nothing, valueLength)

hr = Convert.ToInt32(uHR)

If hr = 0 Then

arrLength = Convert.ToInt32(valueLength)

Dim value(arrLength) As Byte

uHR = headerInfo.GetAttributeByName( _

streamNum, name, pType, value, valueLength)

hr = Convert.ToInt32(uHR)

If hr = 0 Then

Return value

End If

End If

End Function

Note Reading through this code (and the full code available in the download), you will find quite a few conversions between unsigned integers (UInt16, UInt32, and UInt64) and signed integers (Integer/Int32, Long/Int64). The wrapper supplied with the SDK is not CLS-compliant, which means it is not easy to use from outside of C#. I could have just used C# to write my new application, but I almost always use Microsoft?Visual Basic?.NET, and it was worth a little bit of extra code to stay within my language of choice. Note that, in most cases, these conversions could be done within the wrapper itself, ensuring that it was CLS-compliant and easy to use from any Framework language.

As you can see, the availability of the managed code wrapper makes it very easy to use the Windows Media libraries from the Framework. In time I will rewrite my music system to use this wrapper for reading file attributes, but for now I have just created a version of the first sample that uses the new managed wrapper. Other than the actual attribute reading code, the rest of the application is unchanged.

Coding Challenge

At the end of some of my Coding4Fun columns, I will have a little coding challenge梥omething for you to work on if you are interested. For this first article, the challenge is to build your own application that loads and uses MP3/WMA attributes (using my sample as a starting point if you wish). Try to create an application that uses these attributes in some interesting and/or useful way, or create your own code for loading the attributes as an alternative to using the Windows Media libraries. Of course, Framework code is preferred (Visual Basic .NET, C#, or Managed C++ please), but an unmanaged component that exposes a COM interface would also be good.

Just post whatever you produce to the User Samples area of GotDotNet and send me an e-mail message (at duncanma@microsoft.com) with an explanation of what you have done and why you feel it is interesting. I'd appreciate it if you would include a quick blurb on yourself as well, so that I can tell the world about you if I pick your code to mention in an article. Sorry, there are no prizes, but I will respond to the most interesting code I receive, and you will gain the respect and admiration of your peers by posting onto GotDotNet. As far as other submission guidelines, I will follow the rules set forth on the GotDotNet site as our terms of use for your code. (You will need to logon with your Microsoft?Passport credentials to view the upload agreement.) You can send in submissions whenever you like (but always through GotDotNet's upload system please; I don't want my inbox to fill up with code samples!). I am always interested in seeing what code programmers create when they aren't being paid. On that note, check out this cool Framework game Miner Arena, written by some programmers at the Budapest University of Technology and Economics in Hungary. The setup of Miner Arena isn't exactly simple, but the depth of code and architecture explored in this game makes it worth digging into.

Conclusion

If you want to build your own music playing system, then you need to be able to read the attributes of your music files, as demonstrated in the code for this article. In future articles, I will continue with the music player examples, but I will also cover a few other topics, such as games, graphics, network communication, and more. Have your own ideas for hobbyist content? Let me know at duncanma@microsoft.com, and happy coding!

Coding4Fun

Duncan Mackenzie is the Microsoft Visual Basic .NET Content Strategist for MSDN during the day, and a dedicated coder late at night. It has been suggested that he wouldn't be able to do any work at all without his Earl Grey tea, but let's hope we never have to find out. For more on Duncan, see his profile on GotDotNet.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有