2015-08-26 22:42:40 +02:00
|
|
|
|
// (c) Giora Tamir (giora@gtamir.com), 2005
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
|
|
|
|
namespace Nikse.SubtitleEdit.Core.ContainerFormats
|
|
|
|
|
{
|
|
|
|
|
public class RiffDecodeHeader
|
|
|
|
|
{
|
2017-08-20 10:31:30 +02:00
|
|
|
|
private double _frameRate;
|
|
|
|
|
private int _maxBitRate;
|
|
|
|
|
private int _totalFrames;
|
|
|
|
|
private int _numStreams;
|
|
|
|
|
private int _width;
|
|
|
|
|
private int _height;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2017-08-20 10:31:30 +02:00
|
|
|
|
private string _isft;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2017-08-20 10:31:30 +02:00
|
|
|
|
private double _vidDataRate;
|
|
|
|
|
private string _vidHandler;
|
|
|
|
|
private double _audDataRate;
|
|
|
|
|
private string _audHandler;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2017-08-20 10:31:30 +02:00
|
|
|
|
private int _numChannels;
|
|
|
|
|
private int _samplesPerSec;
|
|
|
|
|
private int _bitsPerSec;
|
|
|
|
|
private int _bitsPerSample;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Access the internal parser object
|
|
|
|
|
/// </summary>
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public RiffParser Parser { get; }
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
|
|
|
|
public double FrameRate
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
double rate = 0.0;
|
2017-08-20 10:31:30 +02:00
|
|
|
|
if (_frameRate > 0.0)
|
2019-01-19 14:40:37 +01:00
|
|
|
|
{
|
2017-08-20 10:31:30 +02:00
|
|
|
|
rate = 1000000.0 / _frameRate;
|
2019-01-19 14:40:37 +01:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-26 22:42:40 +02:00
|
|
|
|
return rate;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public string MaxBitRate => $"{_maxBitRate / 128:N} Kb/Sec";
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public int TotalFrames => _totalFrames;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
|
|
|
|
public double TotalMilliseconds
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
double totalTime = 0.0;
|
2017-08-20 10:31:30 +02:00
|
|
|
|
if (_frameRate > 0.0)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
2017-08-20 10:31:30 +02:00
|
|
|
|
totalTime = _totalFrames * _frameRate / TimeCode.BaseUnit;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
|
|
|
|
return totalTime;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public string NumStreams => $"Streams in file: {_numStreams:G}";
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public string FrameSize => $"{_width:G} x {_height:G} pixels per frame";
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public int Width => _width;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public int Height => _height;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public string VideoDataRate => $"Video rate {_vidDataRate:N2} frames/Sec";
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public string AudioDataRate => $"Audio rate {_audDataRate / TimeCode.BaseUnit:N2} Kb/Sec";
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public string VideoHandler => _vidHandler;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public string AudioHandler => $"Audio handler 4CC code: {_audHandler}";
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public string ISFT => _isft;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public string NumChannels => $"Audio channels: {_numChannels}";
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public string SamplesPerSec => $"Audio rate: {_samplesPerSec:N0} Samples/Sec";
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public string BitsPerSec => $"Audio rate: {_bitsPerSec:N0} Bytes/Sec";
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public string BitsPerSample => $"Audio data: {_bitsPerSample:N0} bits/Sample";
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
|
|
|
|
public RiffDecodeHeader(RiffParser rp)
|
|
|
|
|
{
|
2020-02-03 20:53:39 +01:00
|
|
|
|
Parser = rp;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Clear()
|
|
|
|
|
{
|
2017-08-20 10:31:30 +02:00
|
|
|
|
_frameRate = 0;
|
|
|
|
|
_height = 0;
|
|
|
|
|
_maxBitRate = 0;
|
|
|
|
|
_numStreams = 0;
|
|
|
|
|
_totalFrames = 0;
|
|
|
|
|
_width = 0;
|
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
_isft = string.Empty;
|
2017-08-20 10:31:30 +02:00
|
|
|
|
|
|
|
|
|
_vidDataRate = 0;
|
|
|
|
|
_audDataRate = 0;
|
2020-02-03 20:53:39 +01:00
|
|
|
|
_vidHandler = string.Empty;
|
|
|
|
|
_audHandler = string.Empty;
|
2017-08-20 10:31:30 +02:00
|
|
|
|
|
|
|
|
|
_numChannels = 0;
|
|
|
|
|
_samplesPerSec = 0;
|
|
|
|
|
_bitsPerSample = 0;
|
|
|
|
|
_bitsPerSec = 0;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Default list element handler - skip the entire list
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="rp"></param>
|
2017-08-20 10:31:30 +02:00
|
|
|
|
/// <param name="fourCc"></param>
|
2015-08-26 22:42:40 +02:00
|
|
|
|
/// <param name="length"></param>
|
2020-02-03 20:53:39 +01:00
|
|
|
|
private static void ProcessList(RiffParser rp, int fourCc, int length)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
|
|
|
|
rp.SkipData(length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Handle chunk elements found in the AVI file. Ignores unknown chunks and
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="rp"></param>
|
2017-08-20 10:31:30 +02:00
|
|
|
|
/// <param name="fourCc"></param>
|
2015-08-26 22:42:40 +02:00
|
|
|
|
/// <param name="unpaddedLength"></param>
|
|
|
|
|
/// <param name="paddedLength"></param>
|
2017-08-20 10:31:30 +02:00
|
|
|
|
private void ProcessAviChunk(RiffParser rp, int fourCc, int unpaddedLength, int paddedLength)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
2020-02-03 20:53:39 +01:00
|
|
|
|
if (AviRiffData.MainAviHeader == fourCc)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
2020-02-03 20:53:39 +01:00
|
|
|
|
DecodeAviHeader(rp, paddedLength); // Main AVI header
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
2020-02-03 20:53:39 +01:00
|
|
|
|
else if (AviRiffData.AviStreamHeader == fourCc)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
2020-02-03 20:53:39 +01:00
|
|
|
|
DecodeAviStream(rp, paddedLength); // Stream header
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
2020-02-03 20:53:39 +01:00
|
|
|
|
else if (AviRiffData.AviIsft == fourCc)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
2020-02-03 20:53:39 +01:00
|
|
|
|
var ba = new byte[paddedLength];
|
2015-08-26 22:42:40 +02:00
|
|
|
|
rp.ReadData(ba, 0, paddedLength);
|
2020-02-03 20:53:39 +01:00
|
|
|
|
var sb = new StringBuilder(unpaddedLength);
|
2015-08-26 22:42:40 +02:00
|
|
|
|
for (int i = 0; i < unpaddedLength; ++i)
|
|
|
|
|
{
|
2019-01-19 14:40:37 +01:00
|
|
|
|
if (0 != ba[i])
|
|
|
|
|
{
|
|
|
|
|
sb.Append((char)ba[i]);
|
|
|
|
|
}
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-20 10:31:30 +02:00
|
|
|
|
_isft = sb.ToString();
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-02-03 20:53:39 +01:00
|
|
|
|
rp.SkipData(paddedLength); // Unknown chunk - skip
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Handle List elements found in the AVI file. Ignores unknown lists and recursively looks
|
|
|
|
|
/// at the content of known lists.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="rp"></param>
|
2017-08-20 10:31:30 +02:00
|
|
|
|
/// <param name="fourCc"></param>
|
2015-08-26 22:42:40 +02:00
|
|
|
|
/// <param name="length"></param>
|
2017-08-20 10:31:30 +02:00
|
|
|
|
private void ProcessAviList(RiffParser rp, int fourCc, int length)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
2017-08-20 10:31:30 +02:00
|
|
|
|
RiffParser.ProcessChunkElement pac = ProcessAviChunk;
|
|
|
|
|
RiffParser.ProcessListElement pal = ProcessAviList;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
|
|
|
|
// Is this the header?
|
2020-02-03 20:53:39 +01:00
|
|
|
|
if (AviRiffData.AviHeaderList == fourCc || AviRiffData.AviStreamList == fourCc || AviRiffData.InfoList == fourCc)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
|
|
|
|
while (length > 0)
|
|
|
|
|
{
|
2019-01-19 14:40:37 +01:00
|
|
|
|
if (false == rp.ReadElement(ref length, pac, pal))
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-02-03 20:53:39 +01:00
|
|
|
|
rp.SkipData(length); // Unknown lists - ignore
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-20 10:31:30 +02:00
|
|
|
|
public void ProcessMainAvi()
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
|
|
|
|
Clear();
|
|
|
|
|
int length = Parser.DataSize;
|
|
|
|
|
|
2017-08-20 10:31:30 +02:00
|
|
|
|
RiffParser.ProcessChunkElement pdc = ProcessAviChunk;
|
|
|
|
|
RiffParser.ProcessListElement pal = ProcessAviList;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
|
|
|
|
while (length > 0)
|
|
|
|
|
{
|
2019-01-19 14:40:37 +01:00
|
|
|
|
if (false == Parser.ReadElement(ref length, pdc, pal))
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-20 10:31:30 +02:00
|
|
|
|
public static int GetInt(byte[] buffer, int index)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
2017-08-20 10:31:30 +02:00
|
|
|
|
byte[] bytes = { buffer[index + 0], buffer[index + 1], buffer[index + 2], buffer[index + 3] };
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2017-08-20 10:31:30 +02:00
|
|
|
|
if (!BitConverter.IsLittleEndian)
|
2019-01-19 14:40:37 +01:00
|
|
|
|
{
|
2017-08-20 10:31:30 +02:00
|
|
|
|
Array.Reverse(bytes);
|
2019-01-19 14:40:37 +01:00
|
|
|
|
}
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2017-08-20 10:31:30 +02:00
|
|
|
|
return BitConverter.ToInt32(bytes, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
public static short GetShort(byte[] buffer, int index)
|
2017-08-20 10:31:30 +02:00
|
|
|
|
{
|
|
|
|
|
byte[] bytes = { buffer[index + 0], buffer[index + 1] };
|
|
|
|
|
|
|
|
|
|
if (!BitConverter.IsLittleEndian)
|
2019-01-19 14:40:37 +01:00
|
|
|
|
{
|
2017-08-20 10:31:30 +02:00
|
|
|
|
Array.Reverse(bytes);
|
2019-01-19 14:40:37 +01:00
|
|
|
|
}
|
2017-08-20 10:31:30 +02:00
|
|
|
|
|
|
|
|
|
return BitConverter.ToInt16(bytes, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DecodeAviHeader(RiffParser rp, int length)
|
|
|
|
|
{
|
|
|
|
|
var ba = new byte[length];
|
2015-08-26 22:42:40 +02:00
|
|
|
|
if (rp.ReadData(ba, 0, length) != length)
|
|
|
|
|
{
|
|
|
|
|
throw new RiffParserException("Problem reading AVI header.");
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-20 10:31:30 +02:00
|
|
|
|
_frameRate = GetInt(ba, 0);
|
|
|
|
|
_maxBitRate = GetInt(ba, 4);
|
|
|
|
|
_totalFrames = GetInt(ba, 16);
|
|
|
|
|
_numStreams = GetInt(ba, 24);
|
|
|
|
|
_width = GetInt(ba, 32);
|
|
|
|
|
_height = GetInt(ba, 36);
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-20 10:31:30 +02:00
|
|
|
|
private void DecodeAviStream(RiffParser rp, int length)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
2017-08-20 10:31:30 +02:00
|
|
|
|
var ba = new byte[length];
|
2015-08-26 22:42:40 +02:00
|
|
|
|
if (rp.ReadData(ba, 0, length) != length)
|
|
|
|
|
{
|
|
|
|
|
throw new RiffParserException("Problem reading AVI header.");
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
var aviStreamHeader = new AviStreamHeader
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
2020-02-03 20:53:39 +01:00
|
|
|
|
FccType = GetInt(ba, 0),
|
|
|
|
|
FccHandler = GetInt(ba, 4),
|
|
|
|
|
Scale = GetInt(ba, 20),
|
|
|
|
|
Rate = GetInt(ba, 24),
|
|
|
|
|
Start = GetInt(ba, 28),
|
|
|
|
|
Length = GetInt(ba, 32),
|
|
|
|
|
SampleSize = GetInt(ba, 44),
|
2017-08-20 10:31:30 +02:00
|
|
|
|
};
|
2015-08-26 22:42:40 +02:00
|
|
|
|
|
2020-02-03 20:53:39 +01:00
|
|
|
|
if (AviRiffData.StreamTypeVideo == aviStreamHeader.FccType)
|
2017-08-20 10:31:30 +02:00
|
|
|
|
{
|
2020-02-03 20:53:39 +01:00
|
|
|
|
_vidHandler = RiffParser.FromFourCc(aviStreamHeader.FccHandler);
|
|
|
|
|
if (aviStreamHeader.Scale > 0)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
2020-02-03 20:53:39 +01:00
|
|
|
|
_vidDataRate = (double)aviStreamHeader.Rate / aviStreamHeader.Scale;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
2017-08-20 10:31:30 +02:00
|
|
|
|
else
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
2017-08-20 10:31:30 +02:00
|
|
|
|
_vidDataRate = 0.0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-03 20:53:39 +01:00
|
|
|
|
else if (AviRiffData.StreamTypeAudio == aviStreamHeader.FccType)
|
2017-08-20 10:31:30 +02:00
|
|
|
|
{
|
2020-02-03 20:53:39 +01:00
|
|
|
|
if (AviRiffData.Mp3 == aviStreamHeader.FccHandler)
|
2017-08-20 10:31:30 +02:00
|
|
|
|
{
|
|
|
|
|
_audHandler = "MP3";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-02-03 20:53:39 +01:00
|
|
|
|
_audHandler = RiffParser.FromFourCc(aviStreamHeader.FccHandler);
|
2017-08-20 10:31:30 +02:00
|
|
|
|
}
|
2020-02-03 20:53:39 +01:00
|
|
|
|
if (aviStreamHeader.Scale > 0)
|
2017-08-20 10:31:30 +02:00
|
|
|
|
{
|
2020-02-03 20:53:39 +01:00
|
|
|
|
_audDataRate = 8.0 * aviStreamHeader.Rate / aviStreamHeader.Scale;
|
|
|
|
|
if (aviStreamHeader.SampleSize > 0)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
2020-02-03 20:53:39 +01:00
|
|
|
|
_audDataRate /= aviStreamHeader.SampleSize;
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-08-20 10:31:30 +02:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_audDataRate = 0.0;
|
|
|
|
|
}
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-20 10:31:30 +02:00
|
|
|
|
private void ProcessWaveChunk(RiffParser rp, int fourCc, int unpaddedLength, int length)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
|
|
|
|
// Is this a 'fmt' chunk?
|
2020-02-03 20:53:39 +01:00
|
|
|
|
if (AviRiffData.WaveFmt == fourCc)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
|
|
|
|
DecodeWave(rp, length);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
rp.SkipData(length);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-20 10:31:30 +02:00
|
|
|
|
private void DecodeWave(RiffParser rp, int length)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
2017-08-20 10:31:30 +02:00
|
|
|
|
var ba = new byte[length];
|
2015-08-26 22:42:40 +02:00
|
|
|
|
rp.ReadData(ba, 0, length);
|
|
|
|
|
|
2017-08-20 10:31:30 +02:00
|
|
|
|
_numChannels = GetShort(ba, 2);
|
|
|
|
|
_bitsPerSec = GetInt(ba, 8);
|
|
|
|
|
_bitsPerSample = GetShort(ba, 14);
|
|
|
|
|
_samplesPerSec = GetInt(ba, 4);
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-20 10:31:30 +02:00
|
|
|
|
public void ProcessMainWave()
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
|
|
|
|
Clear();
|
|
|
|
|
int length = Parser.DataSize;
|
|
|
|
|
|
|
|
|
|
RiffParser.ProcessChunkElement pdc = ProcessWaveChunk;
|
|
|
|
|
RiffParser.ProcessListElement pal = ProcessList;
|
|
|
|
|
|
|
|
|
|
while (length > 0)
|
|
|
|
|
{
|
2019-01-19 14:40:37 +01:00
|
|
|
|
if (false == Parser.ReadElement(ref length, pdc, pal))
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-08-26 22:42:40 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-01-24 11:51:04 +01:00
|
|
|
|
}
|