65.9K
CodeProject 正在变化。 阅读更多。
Home

MP3 + CDG 卡拉 OK 播放器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (11投票s)

2010年6月25日

GPL3

2分钟阅读

viewsIcon

131400

downloadIcon

7300

MP3 和 CDG 文件播放器。

引言

这是一个 MP3 + CDG 文件播放器应用程序,它解析 CDG 文件并在播放 MP3 文件时实时渲染它们。 作为奖励,我包含了一个 MP3 + CDG 到 AVI 转换应用程序,本文将不会讨论它。

背景

在网上搜索了一段时间后,我注意到没有可用的 .NET 实现来解析 CDG 文件。 一件事导致另一件事,我开始移植我在 Google Code 上找到的一些 C++ 代码。 一旦建立播放,我就想要音高调整。 之后,我想要渲染到 AVI 文件。 之后,我想要 AVI 渲染的透明度和背景视频...

Using the Code

这是播放 MP3 和 CDG 文件的高层工作流程

  1. 将 MP3 和 CDG 文件解压缩到临时目录。
  2. 创建一个流并打开 CDG 文件。
  3. 确定 CDG 文件的持续时间,以便可以定义边界。
  4. 开始播放 MP3 文件。
  5. 渲染 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 - 初始修订版。
© . All rights reserved.