MP3 + CDG 卡拉 OK 播放器






4.93/5 (11投票s)
MP3 和 CDG 文件播放器。
引言
这是一个 MP3 + CDG 文件播放器应用程序,它解析 CDG 文件并在播放 MP3 文件时实时渲染它们。 作为奖励,我包含了一个 MP3 + CDG 到 AVI 转换应用程序,本文将不会讨论它。
背景
在网上搜索了一段时间后,我注意到没有可用的 .NET 实现来解析 CDG 文件。 一件事导致另一件事,我开始移植我在 Google Code 上找到的一些 C++ 代码。 一旦建立播放,我就想要音高调整。 之后,我想要渲染到 AVI 文件。 之后,我想要 AVI 渲染的透明度和背景视频...
Using the Code
这是播放 MP3 和 CDG 文件的高层工作流程
- 将 MP3 和 CDG 文件解压缩到临时目录。
- 创建一个流并打开 CDG 文件。
- 确定 CDG 文件的持续时间,以便可以定义边界。
- 开始播放 MP3 文件。
- 渲染 CDG 文件中与正在播放的 MP3 的经过时间位置对应的帧。
主要的播放渲染块如下所示
Private Sub Play()
Try
If mMP3Stream <> 0 AndAlso Bass.BASS_ChannelIsActive(mMP3Stream) = _
BASSActive.BASS_ACTIVE_PLAYING Then
StopPlayback()
End If
PreProcessFiles()
If mCDGFileName = "" Or mMP3FileName = "" Then
MsgBox("Cannot find a CDG and MP3 file to play together.")
StopPlayback()
Exit Sub
End If
mPaused = False
mStop = False
mFrameCount = 0
mCDGFile = New CDGFile(mCDGFileName)
Dim cdgLength As Long = mCDGFile.getTotalDuration
PlayMP3Bass(mMP3FileName)
Dim startTime As DateTime = Now
Dim endTime = startTime.AddMilliseconds(mCDGFile.getTotalDuration)
Dim millisecondsRemaining As Long = cdgLength
While millisecondsRemaining > 0
If mStop Then
Exit While
End If
millisecondsRemaining = endTime.Subtract(Now).TotalMilliseconds
Dim pos As Long = cdgLength - millisecondsRemaining
While mPaused
endTime = Now.AddMilliseconds(millisecondsRemaining)
Application.DoEvents()
End While
mCDGFile.renderAtPosition(pos)
mFrameCount += 1
mCDGWindow.PictureBox1.Image = mCDGFile.RGBImage
mCDGWindow.PictureBox1.BackColor = CType(mCDGFile.RGBImage, Bitmap).GetPixel(1, 1)
mCDGWindow.PictureBox1.Refresh()
Dim myFrameRate As Single = Math.Round(mFrameCount / (pos / 1000), 1)
Application.DoEvents()
End While
StopPlayback()
Catch ex As Exception
End Try
End Sub
另一个挑战是将从 CDG 库创建的多维整数数组转换为位图。 这是执行转换的代码
Public ReadOnly Property RGBImage(Optional ByVal makeTransparent _
As Boolean = False) As System.Drawing.Image
Get
Dim temp As New MemoryStream
Try
Dim i As Integer = 0
For ri = 0 To CDG_FULL_HEIGHT - 1
For ci = 0 To CDG_FULL_WIDTH - 1
Dim ARGBInt As Integer = m_pSurface.rgbData(ri, ci)
Dim myByte(3) As Byte
myByte = BitConverter.GetBytes(ARGBInt)
temp.Write(myByte, 0, 4)
Next
Next
Catch ex As Exception
'Do nothing (empty bitmap will be returned)
End Try
Dim myBitmap As Bitmap = StreamToBitmap(temp, CDG_FULL_WIDTH, CDG_FULL_HEIGHT)
If makeTransparent Then
myBitmap.MakeTransparent(myBitmap.GetPixel(1, 1))
End If
Return myBitmap
End Get
End Property
Public Shared Function StreamToBitmap(ByRef stream As Stream, _
ByVal width As Integer, ByVal height As Integer) As Bitmap
'create a new bitmap
Dim bmp As New Bitmap(width, height, PixelFormat.Format32bppArgb)
Dim bmpData As BitmapData = bmp.LockBits(New Rectangle(0, 0, width, height), _
ImageLockMode.[WriteOnly], bmp.PixelFormat)
stream.Seek(0, SeekOrigin.Begin)
'copy the stream of pixel
For n As Integer = 0 To stream.Length - 1
Dim myByte(0) As Byte
stream.Read(myByte, 0, 1)
Marshal.WriteByte(bmpData.Scan0, n, myByte(0))
Next
bmp.UnlockBits(bmpData)
Return bmp
End Function
使用了 BASS 音频库进行播放,以便可以实时更改 MP3 的音高以适应歌手的调。 还需要音量控制,以便能够混合 MP3 和歌手麦克风的音量级别。 这是允许播放、音量控制和实时音高调整的代码
Private Sub PlayMP3Bass(ByVal mp3FileName As String)
If mBassInitalized OrElse Bass.BASS_Init(-1, 44100, _
BASSInit.BASS_DEVICE_DEFAULT, Me.Handle) Then
mMP3Stream = 0
mMP3Stream = Bass.BASS_StreamCreateFile(mp3FileName, 0, 0, _
BASSFlag.BASS_STREAM_DECODE Or _
BASSFlag.BASS_SAMPLE_FLOAT Or BASSFlag.BASS_STREAM_PRESCAN)
mMP3Stream = AddOn.Fx.BassFx.BASS_FX_TempoCreate(mMP3Stream, _
BASSFlag.BASS_FX_FREESOURCE Or BASSFlag.BASS_SAMPLE_FLOAT _
Or BASSFlag.BASS_SAMPLE_LOOP)
If mMP3Stream <> 0 Then
AdjustPitch()
AdjustVolume()
ShowCDGWindow()
Bass.BASS_ChannelPlay(mMP3Stream, False)
Else
Throw New Exception(String.Format("Stream error: {0}", _
Bass.BASS_ErrorGetCode()))
End If
End If
End Sub
Private Sub StopPlaybackBass()
Bass.BASS_Stop()
Bass.BASS_StreamFree(mMP3Stream)
Bass.BASS_Free()
mMP3Stream = 0
mBassInitalized = False
End Sub
Private Sub AdjustPitch()
If mMP3Stream <> 0 Then
Bass.BASS_ChannelSetAttribute(mMP3Stream, _
BASSAttribute.BASS_ATTRIB_TEMPO_PITCH, nudKey.Value)
End If
End Sub
Private Sub AdjustVolume()
If mMP3Stream <> 0 Then
Bass.BASS_ChannelSetAttribute(mMP3Stream, _
BASSAttribute.BASS_ATTRIB_VOL, _
If(trbVolume.Value = 0, 0, (trbVolume.Value / 100)))
End If
End Sub
关注点
我要感谢
- Nikolay Nikolov 用 C++ 编写了原始 CDG 代码
- BASS 音频库的作者
- 任何我可能借用过代码片段的人
历史
- 1.0 - 初始修订版。