每周源代码39 – Silverlight3中的Commodore 64 仿真器

[原文发表地址]   The Weekly Source Code 39 - Commodore 64 Emulator in Silverlight 3

[原文发表时间] 2009-03-27 04:26

我很高兴在上周采访了Pete Brown,和他讨论他正在研发的Silverlight 3 Commodore 64 仿真器。他几分钟前刚在CodePlex上发布,但我已经拿着代码玩了好一会儿了。你可以看看Tim Heuer的博文,详细了解怎样着手Silverlight 3 Beta,以及你需要的一些工具,或者你也可以看一些仿真器的相关视频

Silverlight C64 Emulator

记住Pete正在做的是一项完全出于热爱的工作,所有的代码都是在“做出来”模式下写出来的,所以在美学概念上没什么突出的。许多代码都是直接从FrodoSharp C64仿真器中的开放资源C++中直接移植过来的。

其中的确有些非常棒的想法,所以我想在这次的每周源代码中重点关注一下(我承诺我会做更多”每周的”,就从现在开始)。

自动生成视频流

Pete想让屏幕绘制越快越好,最好是50赫兹(一秒50次)。他最初创建PNG和BMP并以尽可能快的速度将其置于屏幕上,然后Silverlight团队中的一个成员提议“做一个视频”。他说的“做个视频”是什么意思呢?他建议用Silverlight MediaElement(“视频播放”控制),以DataSource的形式控制视频。他要动态地生成一个不会结束的影片。

这意味着UI XAML将主要是:

    1: <MediaElement x:Name="VideoDisplay"
    2:   Grid.Row="0"
    3:   Grid.Column="0"
    4:   VerticalAlignment="Top"
    5:   Stretch="Uniform"
    6:   IsHitTestVisible="False"
    7:   Margin="4" />

在背后的代码中,他创建了从MediaStreamSource继承的VideoMediaStreamSource,并在博客中发布:

_

    1: _video = new VideoMediaStreamSource(TheSID.Renderer.AudioStream, C64Display.DISPLAY_X, C64Display.DISPLAY_Y)

看上去是这样的:

    1: private byte[][] _frames = new byte[2][];
    2: public VideoMediaStreamSource(int frameWidth, int frameHeight)
    3: {
    4:     _frameWidth = frameWidth;
    5:     _frameHeight = frameHeight;
    6:  
    7:     _framePixelSize = frameWidth * frameHeight;
    8:     _frameBufferSize = _framePixelSize * BytesPerPixel;
    9:  
   10:      // PAL is 50 frames per second
   11:     _frameTime = (int)TimeSpan.FromSeconds((double)1 / 50).Ticks;
   12:  
   13:     _frames[0] = new byte[_frameBufferSize];
   14:     _frames[1] = new byte[_frameBufferSize]; 
   15:  
   16:     _currentBufferFrame = 0;
   17:     _currentReadyFrame = 1;
   18: }
   19:  
   20:  public void Flip()
   21: {
   22:     int f = _currentBufferFrame;
   23:     _currentBufferFrame = _currentReadyFrame;
   24:     _currentReadyFrame = f;
   25: }

他想向缓冲写一个像素,就像他做低层时常做的那样:

    1: public void WritePixel(int position, Color color)
    2: {
    3:     int offset = position * BytesPerPixel;
    4:  
    5:     _frames[_currentBufferFrame][offset++] = color.B;
    6:     _frames[_currentBufferFrame][offset++] = color.G;
    7:     _frames[_currentBufferFrame][offset++] = color.R;
    8:     _frames[_currentBufferFrame][offset++] = color.A;
    9:  
   10:  }

想要获取样本时,MediaSteamSource会调用GetSampleAsync:

    1: protected override void GetSampleAsync(MediaStreamType mediaStreamType)
    2: {
    3:     if (mediaStreamType == MediaStreamType.Audio)
    4:     {
    5:         GetAudioSample();
    6:     }
    7:     else if (mediaStreamType == MediaStreamType.Video)
    8:     {
    9:         GetVideoSample();
   10:     }
   11: }

他从缓冲中获取视频帧,做了样本并且汇报了他所做的

    1: private void GetVideoSample()
    2: {
    3:     _frameStream = new MemoryStream();
    4:     _frameStream.Write(_frames[_currentReadyFrame], 0, _frameBufferSize);
    5:  
    6:      // Send out the next sample
    7:     MediaStreamSample msSamp = new MediaStreamSample(
    8:         _videoDesc,
    9:         _frameStream,
   10:         0,
   11:         _frameBufferSize,
   12:         _currentVideoTimeStamp,
   13:         _emptySampleDict);
   14:  
   15:      _currentVideoTimeStamp += _frameTime;
   16:  
   17:      ReportGetSampleCompleted(msSamp);
   18: } 

他的应用使得帧数达到最快值,在缓冲中达到50赫兹,MediaElement要求VideoMediaStreamSource中的帧数越快越好。

模拟1541磁盘驱动

在C64仿真器中有一个人人以此为准的叫做.d64的文件格式。D64Drive.cs文件包含了读取映像文件的重要代码。*.D64文件格式相对于软磁盘上所有扇区都有1对1的拷贝。

其中大部分看上去都想C/C++代码,因为它们以前就是。其中一些曾经是“不安全”C#代码,写有不安全关键字,所以运行时可以按指针直接使用。

我很喜欢有类似byte[] magic这样的东西。;)好像每个二进制文件格式都有这些。这样的话,我们要寻找0x43, 0x15, 0x41 and 0x64。注意0x43是"C",而第二第三个字节是"1541",最后是"64"。;)

    1: private void open_close_d64_file(string d64name, Stream fileStream)
    2: {
    3:     long size;
    4:     byte[] magic = new byte[4];
    5:  
    6:     // Close old .d64, if open
    7:     if (the_file != null)
    8:     {
    9:         close_all_channels();
   10:         the_file.Dispose();
   11:         the_file = null;
   12:     }
   13:  
   14:  
   15:     // Open new .d64 file
   16:     if (fileStream != null)
   17:     {
   18:         //the_file = new FileStream(d64name, FileMode.Open, FileAccess.Read);
   19:         the_file = fileStream;
   20:  
   21:         // Check length
   22:         size = the_file.Length;
   23:  
   24:         // Check length
   25:         if (size < NUM_SECTORS * 256)
   26:         {
   27:             the_file.Dispose();
   28:             the_file = null;
   29:             return;
   30:         }
   31:  
   32:         // x64 image?
   33:         the_file.Read(magic, 0, 4);
   34:         if (magic[0] == 0x43 && magic[1] == 0x15 && magic[2] == 0x41 && magic[3] == 0x64)
   35:             image_header = 64;
   36:         else
   37:             image_header = 0;
   38:  
   39:         // Preset error info (all sectors no error)
   40:         Array.Clear(error_info, 0, error_info.Length);
   41:  
   42:         // Load sector error info from .d64 file, if present
   43:         if (image_header == 0 && size == NUM_SECTORS * 257)
   44:         {
   45:             the_file.Seek(NUM_SECTORS * 256, SeekOrigin.Begin);
   46:             the_file.Read(error_info, 0, NUM_SECTORS);
   47:         }
   48:     }
   49: }

这就是完整的有趣过程了,不过Pete在邮件里跟我说:

“附注:我真正的代码绝对不是这样的。这些是C++式的,简单的‘看看能不能行得通’的垃圾。”

那就取其精华吧,棒极了。

参考