disc: cue+mp3/mpc/flac decoding

This commit is contained in:
zeromus 2011-08-08 01:48:31 +00:00
parent d92c27f89d
commit 1f541be6df
10 changed files with 214 additions and 391 deletions

View File

@ -194,6 +194,7 @@
<Compile Include="DiscSystem\CUE_format.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="DiscSystem\Decoding.cs" />
<Compile Include="DiscSystem\Disc.API.cs">
<SubType>Code</SubType>
</Compile>
@ -206,9 +207,6 @@
<Compile Include="DiscSystem\ECM.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="DiscSystem\FFmpeg.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="DiscSystem\Subcode.cs">
<SubType>Code</SubType>
</Compile>

View File

@ -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;
}
}

View File

@ -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);
}
/// <summary>
/// takes posession of the supplied stream
/// </summary>
public void LoadStream(Stream s)
{
Dispose();
BaseStream = s;
readCounter = 0;
BinaryReader br = new BinaryReader(s);
RiffChunk chunk = ReadChunk(br);

View File

@ -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

View File

@ -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;
}
/// <summary>
/// finds audio at a path similar to the provided path (i.e. finds Track01.mp3 for Track01.wav)
/// </summary>
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);
}
}
}

View File

@ -76,16 +76,27 @@ namespace BizHawk.DiscSystem
Array.Copy(temp, 16, buffer, offset, 2048);
}
//main API to determine how many LBA sectors are available
/// <summary>
/// main API to determine how many LBA sectors are available
/// </summary>
public int LBACount { get { return Sectors.Count; } }
//main api for reading the TOC from a disc
/// <summary>
/// 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.
/// </summary>
public bool WasSlowLoad { get; private set; }
/// <summary>
/// main api for reading the TOC from a disc
/// </summary>
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);

View File

@ -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; }
}
}

View File

@ -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;

View File

@ -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();

View File

@ -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);
}
}