From 1f541be6df2e70c8bf429ee2f7260508fe397cdf Mon Sep 17 00:00:00 2001 From: zeromus Date: Mon, 8 Aug 2011 01:48:31 +0000 Subject: [PATCH] disc: cue+mp3/mpc/flac decoding --- BizHawk.Emulation/BizHawk.Emulation.csproj | 4 +- .../DiscSystem/Blobs/Blob_WaveFile.cs | 13 +- .../DiscSystem/Blobs/RiffMaster.cs | 12 +- BizHawk.Emulation/DiscSystem/CUE_format.cs | 20 +- BizHawk.Emulation/DiscSystem/Decoding.cs | 136 +++++++ BizHawk.Emulation/DiscSystem/Disc.API.cs | 15 +- BizHawk.Emulation/DiscSystem/FFmpeg.cs | 373 ------------------ BizHawk.MultiClient/Config.cs | 2 +- BizHawk.MultiClient/MainForm.cs | 1 + DiscoHawk/DiscoHawk.cs | 29 +- 10 files changed, 214 insertions(+), 391 deletions(-) create mode 100644 BizHawk.Emulation/DiscSystem/Decoding.cs delete mode 100644 BizHawk.Emulation/DiscSystem/FFmpeg.cs diff --git a/BizHawk.Emulation/BizHawk.Emulation.csproj b/BizHawk.Emulation/BizHawk.Emulation.csproj index 6f157fe239..aa9d8804a6 100644 --- a/BizHawk.Emulation/BizHawk.Emulation.csproj +++ b/BizHawk.Emulation/BizHawk.Emulation.csproj @@ -194,6 +194,7 @@ Code + Code @@ -206,9 +207,6 @@ Code - - Code - Code diff --git a/BizHawk.Emulation/DiscSystem/Blobs/Blob_WaveFile.cs b/BizHawk.Emulation/DiscSystem/Blobs/Blob_WaveFile.cs index 96eb46d87c..a48fc75929 100644 --- a/BizHawk.Emulation/DiscSystem/Blobs/Blob_WaveFile.cs +++ b/BizHawk.Emulation/DiscSystem/Blobs/Blob_WaveFile.cs @@ -23,13 +23,23 @@ namespace BizHawk.DiscSystem { } + public void Load(byte[] waveData) + { + } + public void Load(string wavePath) + { + var stream = new FileStream(wavePath, FileMode.Open, FileAccess.Read, FileShare.Read); + Load(stream); + } + + public void Load(Stream stream) { try { RiffSource = null; var rm = new RiffMaster(); - rm.LoadFile(wavePath); + rm.LoadStream(stream); RiffSource = rm; //analyze the file to make sure its an OK wave file @@ -69,6 +79,7 @@ namespace BizHawk.DiscSystem catch { Dispose(); + throw; } } diff --git a/BizHawk.Emulation/DiscSystem/Blobs/RiffMaster.cs b/BizHawk.Emulation/DiscSystem/Blobs/RiffMaster.cs index 1f40a82511..3dd832d800 100644 --- a/BizHawk.Emulation/DiscSystem/Blobs/RiffMaster.cs +++ b/BizHawk.Emulation/DiscSystem/Blobs/RiffMaster.cs @@ -18,12 +18,11 @@ class RiffMaster : IDisposable WriteStream(fs); } - public FileStream BaseStream; + public Stream BaseStream; public void LoadFile(string fname) { - Dispose(); - BaseStream = new FileStream(fname, FileMode.Open, FileAccess.Read, FileShare.Read); - LoadStream(BaseStream); + var fs = new FileStream(fname, FileMode.Open, FileAccess.Read, FileShare.Read); + LoadStream(fs); } public void Dispose() @@ -330,8 +329,13 @@ class RiffMaster : IDisposable riff.WriteStream(s); } + /// + /// takes posession of the supplied stream + /// public void LoadStream(Stream s) { + Dispose(); + BaseStream = s; readCounter = 0; BinaryReader br = new BinaryReader(s); RiffChunk chunk = ReadChunk(br); diff --git a/BizHawk.Emulation/DiscSystem/CUE_format.cs b/BizHawk.Emulation/DiscSystem/CUE_format.cs index 668eeaf51c..a0455054f5 100644 --- a/BizHawk.Emulation/DiscSystem/CUE_format.cs +++ b/BizHawk.Emulation/DiscSystem/CUE_format.cs @@ -48,23 +48,35 @@ namespace BizHawk.DiscSystem else if (cue_file.FileType == Cue.CueFileType.Wave) { Blob_WaveFile blob = new Blob_WaveFile(); + try { - blob.Load(blobPath); + //check for the specified file + if (!File.Exists(blobPath)) + { + //if it doesn't exist, then it may be encoded. + AudioDecoder dec = new AudioDecoder(); + byte[] buf = dec.AcquireWaveData(blobPath); + blob.Load(new MemoryStream(buf)); + WasSlowLoad = true; + } + else + { + blob.Load(blobPath); + } } catch (Exception ex) { throw new DiscReferenceException(blobPath, ex); } - blob_length_lba = (int)(blob.Length / blob_sectorsize); - blob_leftover = (int)(blob.Length - blob_length_lba * blob_sectorsize); + blob_length_lba = (int) (blob.Length/blob_sectorsize); + blob_leftover = (int) (blob.Length - blob_length_lba*blob_sectorsize); cue_blob = blob; } else throw new DiscReferenceException(blobPath, new InvalidOperationException("unknown cue file type: " + cue_file.StrFileType)); //TODO - make CueTimestamp better, and also make it a struct, and also just make it DiscTimestamp - //TODO - wav handling //TODO - mp3 decode //start timekeeping for the blob. every time we hit an index, this will advance diff --git a/BizHawk.Emulation/DiscSystem/Decoding.cs b/BizHawk.Emulation/DiscSystem/Decoding.cs new file mode 100644 index 0000000000..94bb63280f --- /dev/null +++ b/BizHawk.Emulation/DiscSystem/Decoding.cs @@ -0,0 +1,136 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.IO; +using System.Collections.Generic; + +namespace BizHawk.DiscSystem +{ + public class FFMpeg + { + public static string FFMpegPath; + + public class AudioQueryResult + { + public bool IsAudio; + } + + static string[] Escape(string[] args) + { + return args.Select(s => s.Contains(" ") ? string.Format("\"{0}\"", s) : s).ToArray(); + } + + static Regex rxHasAudio = new Regex(@"Stream \#(\d*\.\d*)\: Audio", RegexOptions.Compiled); + public AudioQueryResult QueryAudio(string path) + { + var ret = new AudioQueryResult(); + string stdout = Run("-i", path); + ret.IsAudio = rxHasAudio.Matches(stdout).Count > 0; + return ret; + } + + string Run(params string[] args) + { + args = Escape(args); + StringBuilder sbCmdline = new StringBuilder(); + for (int i = 0; i < args.Length; i++) + { + sbCmdline.Append(args[i]); + if (i != args.Length - 1) sbCmdline.Append(' '); + } + + ProcessStartInfo oInfo = new ProcessStartInfo(FFMpegPath, sbCmdline.ToString()); + oInfo.UseShellExecute = false; + oInfo.CreateNoWindow = true; + oInfo.RedirectStandardOutput = true; + oInfo.RedirectStandardError = true; + + Process proc = System.Diagnostics.Process.Start(oInfo); + proc.WaitForExit(); + string result = proc.StandardError.ReadToEnd(); + + return result; + } + + public byte[] DecodeAudio(string path) + { + string tempfile = Path.GetTempFileName(); + try + { + string runResults = Run("-i", path, "-f", "wav", "-y", tempfile); + byte[] ret = File.ReadAllBytes(tempfile); + if (ret.Length == 0) + throw new InvalidOperationException("Failure running ffmpeg for audio decode. here was its output:\r\n" + runResults); + return ret; + } + finally + { + File.Delete(tempfile); + } + } + } + + class AudioDecoder + { + public class AudioDecoder_Exception : Exception + { + public AudioDecoder_Exception(string message) + : base(message) + { + } + } + + public AudioDecoder() + { + } + + bool CheckForAudio(string path) + { + FFMpeg ffmpeg = new FFMpeg(); + var qa = ffmpeg.QueryAudio(path); + return qa.IsAudio; + } + + /// + /// finds audio at a path similar to the provided path (i.e. finds Track01.mp3 for Track01.wav) + /// + string FindAudio(string audioPath) + { + string basePath = Path.GetFileNameWithoutExtension(audioPath); + //look for potential candidates + var di = new DirectoryInfo(Path.GetDirectoryName(audioPath)); + var fis = di.GetFiles(); + //first, look for the file type we actually asked for + foreach (var fi in fis) + { + if (fi.FullName.ToUpper() == audioPath.ToUpper()) + if (CheckForAudio(fi.FullName)) + return fi.FullName; + } + //then look for any other type + foreach (var fi in fis) + { + if (Path.GetFileNameWithoutExtension(fi.FullName) == basePath) + { + if (CheckForAudio(fi.FullName)) + return fi.FullName; + } + } + return null; + } + + public byte[] AcquireWaveData(string audioPath) + { + string path = FindAudio(audioPath); + if (path == null) + { + throw new AudioDecoder_Exception("Could not find source audio for: " + Path.GetFileName(audioPath)); + } + FFMpeg ffmpeg = new FFMpeg(); + return ffmpeg.DecodeAudio(path); + } + + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/DiscSystem/Disc.API.cs b/BizHawk.Emulation/DiscSystem/Disc.API.cs index 66b14f4d07..2ee9867568 100644 --- a/BizHawk.Emulation/DiscSystem/Disc.API.cs +++ b/BizHawk.Emulation/DiscSystem/Disc.API.cs @@ -76,16 +76,27 @@ namespace BizHawk.DiscSystem Array.Copy(temp, 16, buffer, offset, 2048); } - //main API to determine how many LBA sectors are available + /// + /// main API to determine how many LBA sectors are available + /// public int LBACount { get { return Sectors.Count; } } - //main api for reading the TOC from a disc + /// + /// indicates whether this disc took significant work to load from the disc (i.e. decoding of ECM or audio data) + /// In this case, the user may appreciate a prompt to export the disc so that it won't take so long next time. + /// + public bool WasSlowLoad { get; private set; } + + /// + /// main api for reading the TOC from a disc + /// public DiscTOC ReadTOC() { return TOC; } // converts LBA to minute:second:frame format. + //TODO - somewhat redundant with CueTimestamp, which is due for refactoring into something not cue-related public static void ConvertLBAtoMSF(int lba, out byte m, out byte s, out byte f) { m = (byte) (lba / 75 / 60); diff --git a/BizHawk.Emulation/DiscSystem/FFmpeg.cs b/BizHawk.Emulation/DiscSystem/FFmpeg.cs deleted file mode 100644 index 1a500ce165..0000000000 --- a/BizHawk.Emulation/DiscSystem/FFmpeg.cs +++ /dev/null @@ -1,373 +0,0 @@ -//http://jasonjano.wordpress.com/2010/02/09/a-simple-c-wrapper-for-ffmpeg/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.IO; -using System.Diagnostics; -using System.Configuration; -using System.Text.RegularExpressions; - -namespace ffMpeg -{ - public class Converter - { - public static string _ffExe; - public string WorkingPath; //i.e. temp - - public Converter() - { - Initialize(); - } - - #region Initialization - private void Initialize() - { - } - - private string GetWorkingFile() - { - //try the stated directory - if (File.Exists(_ffExe)) - { - return _ffExe; - } - - //oops, that didn't work, try the base directory - if (File.Exists(Path.GetFileName(_ffExe))) - { - return Path.GetFileName(_ffExe); - } - - //well, now we are really unlucky, let's just return null - return null; - } - #endregion - - #region Get the File without creating a file lock - public static System.Drawing.Image LoadImageFromFile(string fileName) - { - System.Drawing.Image theImage = null; - using (FileStream fileStream = new FileStream(fileName, FileMode.Open, - FileAccess.Read)) - { - byte[] img; - img = new byte[fileStream.Length]; - fileStream.Read(img, 0, img.Length); - fileStream.Close(); - theImage = System.Drawing.Image.FromStream(new MemoryStream(img)); - img = null; - } - GC.Collect(); - return theImage; - } - - public static MemoryStream LoadMemoryStreamFromFile(string fileName) - { - MemoryStream ms = null; - using (FileStream fileStream = new FileStream(fileName, FileMode.Open, - FileAccess.Read)) - { - byte[] fil; - fil = new byte[fileStream.Length]; - fileStream.Read(fil, 0, fil.Length); - fileStream.Close(); - ms = new MemoryStream(fil); - } - GC.Collect(); - return ms; - } - #endregion - - #region Run the process - private string RunProcess(string Parameters) - { - //create a process info - ProcessStartInfo oInfo = new ProcessStartInfo(_ffExe, Parameters); - oInfo.UseShellExecute = false; - oInfo.CreateNoWindow = true; - oInfo.RedirectStandardOutput = true; - oInfo.RedirectStandardError = true; - - //Create the output and streamreader to get the output - string output = null; StreamReader srOutput = null; - - //try the process - try - { - //run the process - Process proc = System.Diagnostics.Process.Start(oInfo); - - proc.WaitForExit(); - - //get the output - srOutput = proc.StandardError; - - //now put it in a string - output = srOutput.ReadToEnd(); - - proc.Close(); - } - catch (Exception) - { - output = string.Empty; - } - finally - { - //now, if we succeded, close out the streamreader - if (srOutput != null) - { - srOutput.Close(); - srOutput.Dispose(); - } - } - return output; - } - #endregion - - #region GetVideoInfo - public VideoFile GetVideoInfo(MemoryStream inputFile, string Filename) - { - string tempfile = Path.Combine(this.WorkingPath, System.Guid.NewGuid().ToString() + Path.GetExtension(Filename)); - FileStream fs = File.Create(tempfile); - inputFile.WriteTo(fs); - fs.Flush(); - fs.Close(); - GC.Collect(); - - VideoFile vf = null; - try - { - vf = new VideoFile(tempfile); - } - catch (Exception ex) - { - throw ex; - } - - GetVideoInfo(vf); - - try - { - File.Delete(tempfile); - } - catch (Exception) - { - - } - - return vf; - } - public VideoFile GetVideoInfo(string inputPath) - { - VideoFile vf = null; - try - { - vf = new VideoFile(inputPath); - } - catch (Exception ex) - { - throw ex; - } - GetVideoInfo(vf); - return vf; - } - public void GetVideoInfo(VideoFile input) - { - //set up the parameters for video info - string Params = string.Format("-i \"{0}\"", input.Path); - string output = RunProcess(Params); - input.RawInfo = output; - - //get duration - Regex re = new Regex("[D|d]uration:.((\\d|:|\\.)*)"); - Match m = re.Match(input.RawInfo); - - if (m.Success) - { - string duration = m.Groups[1].Value; - string[] timepieces = duration.Split(new char[] { ':', '.' }); - if (timepieces.Length == 4) - { - input.Duration = new TimeSpan(0, Convert.ToInt16(timepieces[0]), Convert.ToInt16(timepieces[1]), Convert.ToInt16(timepieces[2]), Convert.ToInt16(timepieces[3])); - } - } - - //get audio bit rate - re = new Regex("[B|b]itrate:.((\\d|:)*)"); - m = re.Match(input.RawInfo); - double kb = 0.0; - if (m.Success) - { - Double.TryParse(m.Groups[1].Value, out kb); - } - input.BitRate = kb; - - //get the audio format - re = new Regex("[A|a]udio:.*"); - m = re.Match(input.RawInfo); - if (m.Success) - { - input.AudioFormat = m.Value; - } - - //get the video format - re = new Regex("[V|v]ideo:.*"); - m = re.Match(input.RawInfo); - if (m.Success) - { - input.VideoFormat = m.Value; - } - - //get the video format - re = new Regex("(\\d{2,3})x(\\d{2,3})"); - m = re.Match(input.RawInfo); - if (m.Success) - { - int width = 0; int height = 0; - int.TryParse(m.Groups[1].Value, out width); - int.TryParse(m.Groups[2].Value, out height); - input.Width = width; - input.Height = height; - } - input.infoGathered = true; - } - #endregion - - #region Convert to FLV - public OutputPackage ConvertToFLV(MemoryStream inputFile, string Filename) - { - string tempfile = Path.Combine(this.WorkingPath, System.Guid.NewGuid().ToString() + Path.GetExtension(Filename)); - FileStream fs = File.Create(tempfile); - inputFile.WriteTo(fs); - fs.Flush(); - fs.Close(); - GC.Collect(); - - VideoFile vf = null; - try - { - vf = new VideoFile(tempfile); - } - catch (Exception ex) - { - throw ex; - } - - OutputPackage oo = ConvertToFLV(vf); - - try - { - File.Delete(tempfile); - } - catch (Exception) - { - - } - - return oo; - } - public OutputPackage ConvertToFLV(string inputPath) - { - VideoFile vf = null; - try - { - vf = new VideoFile(inputPath); - } - catch (Exception ex) - { - throw ex; - } - - OutputPackage oo = ConvertToFLV(vf); - return oo; - } - public OutputPackage ConvertToFLV(VideoFile input) - { - if (!input.infoGathered) - { - GetVideoInfo(input); - } - OutputPackage ou = new OutputPackage(); - - string filename = System.Guid.NewGuid().ToString() + ".flv"; - string finalpath = Path.Combine(this.WorkingPath, filename); - string Params = string.Format("-i \"{0}\" -y -ar 22050 -ab 64 -f flv \"{1}\"", input.Path, finalpath); - string output = RunProcess(Params); - - if (File.Exists(finalpath)) - { - ou.VideoStream = LoadMemoryStreamFromFile(finalpath); - try - { - File.Delete(finalpath); - } - catch (Exception) { } - } - return ou; - } - #endregion - } - - public class VideoFile - { - #region Properties - private string _Path; - public string Path - { - get - { - return _Path; - } - set - { - _Path = value; - } - } - - public TimeSpan Duration { get; set; } - public double BitRate { get; set; } - public string AudioFormat { get; set; } - public string VideoFormat { get; set; } - public int Height { get; set; } - public int Width { get; set; } - public string RawInfo { get; set; } - public bool infoGathered { get; set; } - #endregion - - #region Constructors - public VideoFile(string path) - { - _Path = path; - Initialize(); - } - #endregion - - #region Initialization - private void Initialize() - { - this.infoGathered = false; - //first make sure we have a value for the video file setting - if (string.IsNullOrEmpty(_Path)) - { - throw new Exception("Could not find the location of the video file"); - } - - //Now see if the video file exists - if (!File.Exists(_Path)) - { - throw new Exception("The video file " + _Path + " does not exist."); - } - } - #endregion - } - - public class OutputPackage - { - public MemoryStream VideoStream { get; set; } - public System.Drawing.Image PreviewImage { get; set; } - public string RawOutput { get; set; } - public bool Success { get; set; } - } -} diff --git a/BizHawk.MultiClient/Config.cs b/BizHawk.MultiClient/Config.cs index b48ab4c4fe..4eaca5dc7e 100644 --- a/BizHawk.MultiClient/Config.cs +++ b/BizHawk.MultiClient/Config.cs @@ -90,7 +90,7 @@ //BIOS Paths public string PathPCEBios = ".\\PCECDBios.pce"; //TODO: better default filename - public string FFMpegPath = "ffmpeg.exe"; + public string FFMpegPath = "%exe%/ffmpeg.exe"; // General Client Settings public int TargetZoomFactor = 2; diff --git a/BizHawk.MultiClient/MainForm.cs b/BizHawk.MultiClient/MainForm.cs index 70b71d521d..4f5e74494c 100644 --- a/BizHawk.MultiClient/MainForm.cs +++ b/BizHawk.MultiClient/MainForm.cs @@ -73,6 +73,7 @@ namespace BizHawk.MultiClient LogConsole.ShowConsole(); displayLogWindowToolStripMenuItem.Checked = true; } + DiscSystem.FFMpeg.FFMpegPath = PathManager.MakeProgramRelativePath(Global.Config.FFMpegPath); Global.CheatList = new CheatList(); UpdateStatusSlots(); diff --git a/DiscoHawk/DiscoHawk.cs b/DiscoHawk/DiscoHawk.cs index cd1a7e2784..dbe6195b8f 100644 --- a/DiscoHawk/DiscoHawk.cs +++ b/DiscoHawk/DiscoHawk.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using System.Collections.Generic; using System.IO; @@ -13,9 +14,20 @@ namespace BizHawk { class DiscoHawk { + + public static string GetExeDirectoryAbsolute() + { + string p = Path.GetDirectoryName(Assembly.GetEntryAssembly().GetName().CodeBase); + if (p.Substring(0, 6) == "file:\\") + p = p.Remove(0, 6); + string z = p; + return p; + } + [STAThread] static void Main(string[] args) { + DiscSystem.FFMpeg.FFMpegPath = Path.Combine(GetExeDirectoryAbsolute(), "ffmpeg.exe"); new DiscoHawk().Run(args); } @@ -91,7 +103,18 @@ namespace BizHawk //} //DiscSystem.Disc disc = DiscSystem.Disc.FromCuePath(@"D:\discs\Bomberman_'94_Taikenban_(SCD)(JPN)_-_wav'd\Bomberman '94 Taikenban (SCD)(JPN)_hawked.cue"); - DiscSystem.Disc disc = DiscSystem.Disc.FromCuePath(@"D:\discs\Bomberman_'94_Taikenban_(SCD)(JPN)_-_wav'd\Bomberman '94 Taikenban (SCD)(JPN).cue"); + //DiscSystem.Disc disc = DiscSystem.Disc.FromCuePath(@"D:\discs\Bomberman_'94_Taikenban_(SCD)(JPN)_-_wav'd\Bomberman '94 Taikenban (SCD)(JPN).cue"); + //var prefs = new DiscSystem.CueBinPrefs(); + //prefs.AnnotateCue = false; + //prefs.OneBlobPerTrack = false; + //prefs.ReallyDumpBin = true; + //prefs.OmitRedundantIndex0 = true; + //prefs.SingleSession = true; + ////var cueBin = disc.DumpCueBin("Bomberman '94 Taikenban (SCD)(JPN)_hawked_hawked", prefs); + //var cueBin = disc.DumpCueBin("Bomberman '94 Taikenban (SCD)(JPN)_hawked", prefs); + //cueBin.Dump(@"D:\discs\Bomberman_'94_Taikenban_(SCD)(JPN)_-_wav'd", prefs); + + DiscSystem.Disc disc = DiscSystem.Disc.FromCuePath(@"D:\discs\Angels II - Holy Night (J)[Prototype]\Angels II - Holy Night (J)[Prototype].cue"); var prefs = new DiscSystem.CueBinPrefs(); prefs.AnnotateCue = false; prefs.OneBlobPerTrack = false; @@ -99,8 +122,8 @@ namespace BizHawk prefs.OmitRedundantIndex0 = true; prefs.SingleSession = true; //var cueBin = disc.DumpCueBin("Bomberman '94 Taikenban (SCD)(JPN)_hawked_hawked", prefs); - var cueBin = disc.DumpCueBin("Bomberman '94 Taikenban (SCD)(JPN)_hawked", prefs); - cueBin.Dump(@"D:\discs\Bomberman_'94_Taikenban_(SCD)(JPN)_-_wav'd", prefs); + var cueBin = disc.DumpCueBin("test", prefs); + cueBin.Dump(@"D:\discs\Angels II - Holy Night (J)[Prototype]", prefs); } }