2011-08-08 01:48:31 +00:00
|
|
|
|
using System;
|
2013-11-15 00:49:19 +00:00
|
|
|
|
using System.Collections.Generic;
|
2011-08-08 01:48:31 +00:00
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
2013-11-03 23:45:44 +00:00
|
|
|
|
namespace BizHawk.Emulation.DiscSystem
|
2011-08-08 01:48:31 +00:00
|
|
|
|
{
|
|
|
|
|
public class FFMpeg
|
|
|
|
|
{
|
|
|
|
|
public static string FFMpegPath;
|
|
|
|
|
|
|
|
|
|
public class AudioQueryResult
|
|
|
|
|
{
|
|
|
|
|
public bool IsAudio;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-15 00:49:19 +00:00
|
|
|
|
private static string[] Escape(IEnumerable<string> args)
|
2011-08-08 01:48:31 +00:00
|
|
|
|
{
|
2019-03-20 05:24:33 +00:00
|
|
|
|
return args.Select(s => s.Contains(" ") ? $"\"{s}\"" : s).ToArray();
|
2011-08-08 01:48:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-06-09 01:47:14 +00:00
|
|
|
|
//note: accepts . or : in the stream stream/substream separator in the stream ID format, since that changed at some point in FFMPEG history
|
|
|
|
|
//if someone has a better idea how to make the determination of whether an audio stream is available, I'm all ears
|
|
|
|
|
static readonly Regex rxHasAudio = new Regex(@"Stream \#(\d*(\.|\:)\d*)\: Audio", RegexOptions.Compiled);
|
2011-08-08 01:48:31 +00:00
|
|
|
|
public AudioQueryResult QueryAudio(string path)
|
|
|
|
|
{
|
|
|
|
|
var ret = new AudioQueryResult();
|
2015-09-10 21:54:02 +00:00
|
|
|
|
string stdout = Run("-i", path).Text;
|
2011-08-08 01:48:31 +00:00
|
|
|
|
ret.IsAudio = rxHasAudio.Matches(stdout).Count > 0;
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-08 02:02:01 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// queries whether this service is available. if ffmpeg is broken or missing, then you can handle it gracefully
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool QueryServiceAvailable()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2015-09-10 21:54:02 +00:00
|
|
|
|
string stdout = Run("-version").Text;
|
2011-08-08 02:02:01 +00:00
|
|
|
|
if (stdout.Contains("ffmpeg version")) return true;
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-10 21:54:02 +00:00
|
|
|
|
public struct RunResults
|
|
|
|
|
{
|
|
|
|
|
public string Text;
|
|
|
|
|
public int ExitCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public RunResults Run(params string[] args)
|
2011-08-08 01:48:31 +00:00
|
|
|
|
{
|
|
|
|
|
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(' ');
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-15 00:49:19 +00:00
|
|
|
|
ProcessStartInfo oInfo = new ProcessStartInfo(FFMpegPath, sbCmdline.ToString())
|
|
|
|
|
{
|
|
|
|
|
UseShellExecute = false,
|
|
|
|
|
CreateNoWindow = true,
|
|
|
|
|
RedirectStandardOutput = true,
|
|
|
|
|
RedirectStandardError = true
|
|
|
|
|
};
|
2011-08-08 01:48:31 +00:00
|
|
|
|
|
2013-11-04 03:12:50 +00:00
|
|
|
|
Process proc = Process.Start(oInfo);
|
2014-05-08 03:18:00 +00:00
|
|
|
|
string result = proc.StandardOutput.ReadToEnd();
|
|
|
|
|
result += proc.StandardError.ReadToEnd();
|
2012-04-19 20:04:52 +00:00
|
|
|
|
proc.WaitForExit();
|
2011-08-08 01:48:31 +00:00
|
|
|
|
|
2015-09-10 21:54:02 +00:00
|
|
|
|
return new RunResults
|
|
|
|
|
{
|
|
|
|
|
ExitCode = proc.ExitCode,
|
|
|
|
|
Text = result
|
|
|
|
|
};
|
2011-08-08 01:48:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] DecodeAudio(string path)
|
|
|
|
|
{
|
|
|
|
|
string tempfile = Path.GetTempFileName();
|
|
|
|
|
try
|
|
|
|
|
{
|
2015-09-10 21:54:02 +00:00
|
|
|
|
var runResults = Run("-i", path, "-xerror", "-f", "wav", "-ar", "44100", "-ac", "2", "-acodec", "pcm_s16le", "-y", tempfile);
|
|
|
|
|
if(runResults.ExitCode != 0)
|
2019-03-20 05:24:33 +00:00
|
|
|
|
throw new InvalidOperationException($"Failure running ffmpeg for audio decode. here was its output:\r\n{runResults.Text}");
|
2011-08-08 01:48:31 +00:00
|
|
|
|
byte[] ret = File.ReadAllBytes(tempfile);
|
|
|
|
|
if (ret.Length == 0)
|
2019-03-20 05:24:33 +00:00
|
|
|
|
throw new InvalidOperationException($"Failure running ffmpeg for audio decode. here was its output:\r\n{runResults.Text}");
|
2011-08-08 01:48:31 +00:00
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
File.Delete(tempfile);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class AudioDecoder
|
|
|
|
|
{
|
2013-11-15 00:49:19 +00:00
|
|
|
|
[Serializable]
|
2011-08-08 01:48:31 +00:00
|
|
|
|
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)
|
2015-07-08 08:07:30 +00:00
|
|
|
|
/// TODO - isnt this redundant with CueFileResolver?
|
2011-08-08 01:48:31 +00:00
|
|
|
|
/// </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)
|
|
|
|
|
{
|
2011-08-08 06:14:05 +00:00
|
|
|
|
if (Path.GetFileNameWithoutExtension(fi.FullName).ToUpper() == basePath.ToUpper())
|
2011-08-08 01:48:31 +00:00
|
|
|
|
{
|
|
|
|
|
if (CheckForAudio(fi.FullName))
|
2013-11-15 00:49:19 +00:00
|
|
|
|
{
|
2011-08-08 01:48:31 +00:00
|
|
|
|
return fi.FullName;
|
2013-11-15 00:49:19 +00:00
|
|
|
|
}
|
2011-08-08 01:48:31 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] AcquireWaveData(string audioPath)
|
|
|
|
|
{
|
|
|
|
|
string path = FindAudio(audioPath);
|
|
|
|
|
if (path == null)
|
|
|
|
|
{
|
2019-03-20 05:24:33 +00:00
|
|
|
|
throw new AudioDecoder_Exception($"Could not find source audio for: {Path.GetFileName(audioPath)}");
|
2011-08-08 01:48:31 +00:00
|
|
|
|
}
|
2013-11-15 00:49:19 +00:00
|
|
|
|
return new FFMpeg().DecodeAudio(path);
|
2011-08-08 01:48:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|