每周源代码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,以及你需要的一些工具,或者你也可以看一些仿真器的相关视频。
记住Pete正在做的是一项完全出于热爱的工作,所有的代码都是在“做出来”模式下写出来的,所以在美学概念上没什么突出的。许多代码都是直接从Frodo或Sharp 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++式的,简单的‘看看能不能行得通’的垃圾。”
那就取其精华吧,棒极了。
参考
- Frodo 仿真器: https://frodo.cebix.net/
- SharpC64: https://sourceforge.net/ projects/sharp-c64
- 仿真器视频