每周源代码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++式的,简单的‘看看能不能行得通’的垃圾。”

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

参考

 

Comments (0)

Skip to main content