我開始擴展文森特的答案來解決我注意到的一個問題:當從30fps縮放到100fps時,幀0重複一次第四次(0000 111 222 3333
的幀格式)當我期待000 111 2222
。沒什麼大不了的,因爲它可能只是一個偏好問題(不管你是希望在偶數幀還是奇數幀上發生小數「調整」),但是隨後我進入了兔子洞並構建了一個可以處理任何事情的迭代器類場景,包括分數幀率。
(使用一個通用的迭代具有不需要幀是string
額外的獎勵 - 如果你想代表每一幀爲一類,你能做到這一點。)
public sealed class FramerateScaler<T> : IEnumerable<T>
{
private IEnumerable<T> _source;
private readonly double _inputRate;
private readonly double _outputRate;
private readonly int _startIndex;
public double InputRate { get { return _inputRate; } }
public double OutputRate { get { return _outputRate; } }
public int StartIndex { get { return _startIndex; } }
public TimeSpan InputDuration {
get { return TimeSpan.FromSeconds((1/_inputRate) * (_source.Count() - StartIndex)); }
}
public TimeSpan OutputDuration {
get { return TimeSpan.FromSeconds((1/_outputRate) * this.Count()); }
}
public FramerateScaler(
double inputRate, double outputRate,
IEnumerable<T> source, int startIndex = 0)
{
_source = source;
_inputRate = inputRate;
_outputRate = outputRate;
_startIndex = startIndex;
}
public IEnumerator<T> GetEnumerator()
{
return new ScalingFrameEnumerator<T>(_inputRate, _outputRate, _source, _startIndex);
}
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)GetEnumerator();
}
private sealed class ScalingFrameEnumerator<T> : IEnumerator<T>
{
internal readonly double _inputRate;
internal readonly double _outputRate;
internal readonly int _startIndex;
private readonly List<T> _source;
private readonly double _rateScaleFactor;
private readonly int _totalOutputFrames;
private int _currentOutputFrame = 0;
public ScalingFrameEnumerator(
double inputRate, double outputRate,
IEnumerable<T> source, int startIndex)
{
_inputRate = inputRate;
_outputRate = outputRate;
_source = source.ToList();
_startIndex = startIndex;
_rateScaleFactor = _outputRate/_inputRate;
// Calculate total output frames from input duration
_totalOutputFrames = (int)Math.Round(
(_source.Count - startIndex) * _rateScaleFactor, 0);
}
public T Current
{
get
{
return _source[_startIndex +
(int)Math.Ceiling(_currentOutputFrame/_rateScaleFactor) - 1];
}
}
public void Dispose()
{
// Nothing unmanaged to dispose
}
object IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
_currentOutputFrame++;
return ((_currentOutputFrame - 1) < _totalOutputFrames);
}
public void Reset()
{
_currentOutputFrame = 0;
}
}
}
而且一測試覆蓋冪等,擴大,縮小,以及分數幀率設置:
[TestClass]
public class Test
{
private readonly List<string> _originalFrames = new List<string>();
public Test()
{
// 30 FPS for 10 seconds
for (int f = 0; f < 300; f++)
{
_originalFrames.Add(string.Format("{0:0000000}.png", f));
}
}
[TestMethod]
public void Should_set_default_values()
{
var scaler = new FramerateScaler<string>(30, 30, _originalFrames, 10);
Assert.AreEqual(30, scaler.InputRate);
Assert.AreEqual(30, scaler.OutputRate);
Assert.AreEqual(10, scaler.StartIndex);
Assert.AreEqual(_originalFrames.ElementAt(10), scaler.First());
}
[TestMethod]
public void Scale_from_same_is_idempotent()
{
var scaler = new FramerateScaler<string>(30, 30, _originalFrames);
Assert.AreEqual(scaler.InputDuration, scaler.OutputDuration);
Assert.AreEqual(_originalFrames.Count, scaler.Count());
Assert.IsTrue(_originalFrames.SequenceEqual(scaler));
}
[TestMethod]
public void Scale_from_same_offset_by_half_is_idempotent()
{
var scaler = new FramerateScaler<string>(
30, 30, _originalFrames, _originalFrames.Count/2);
Assert.AreEqual(150, scaler.Count());
Assert.AreEqual(scaler.OutputDuration, scaler.InputDuration);
Assert.IsTrue(_originalFrames
.Skip(150)
.SequenceEqual(scaler));
}
[TestMethod]
public void Scale_from_30_to_60()
{
var scaler = new FramerateScaler<string>(30, 60, _originalFrames);
Assert.AreEqual(600, scaler.Count());
Assert.AreEqual(scaler.OutputDuration, scaler.InputDuration);
var result = scaler.ToList();
Assert.IsTrue(_originalFrames
.Concat(_originalFrames)
.OrderBy(x => x)
.SequenceEqual(scaler));
}
[TestMethod]
public void Scale_from_30_to_60_offset_by_half()
{
var scaler = new FramerateScaler<string>(
30, 60, _originalFrames, _originalFrames.Count/2);
Assert.AreEqual(300, scaler.Count());
Assert.AreEqual(scaler.OutputDuration, scaler.InputDuration);
Assert.IsTrue(_originalFrames
.Skip(150)
.Concat(_originalFrames.Skip(150))
.OrderBy(x => x)
.SequenceEqual(scaler));
}
[TestMethod]
public void Scale_from_30_to_100()
{
var scaler = new FramerateScaler<string>(30, 100, _originalFrames);
Assert.AreEqual(1000, scaler.Count());
Assert.AreEqual(scaler.OutputDuration, scaler.InputDuration);
// 000 - 111 - 2222 ...
Assert.IsTrue(scaler.PatternIs(0, 0, 0, 1, 1, 1, 2, 2, 2, 2));
}
[TestMethod]
public void Scale_from_30_to_100_offset_by_half()
{
var scaler = new FramerateScaler<string>(
30, 100, _originalFrames, _originalFrames.Count/2);
Assert.AreEqual(500, scaler.Count());
Assert.AreEqual(scaler.OutputDuration, scaler.InputDuration);
// 000 - 111 - 2222 ...
Assert.IsTrue(scaler.PatternIs(0, 0, 0, 1, 1, 1, 2, 2, 2, 2));
}
[TestMethod]
public void Scale_from_24p_to_ntsc()
{
var scaler = new FramerateScaler<string>(23.967, 29.97, _originalFrames);
Assert.AreEqual(375, scaler.Count());
Assert.AreEqual(
scaler.OutputDuration.TotalMilliseconds,
scaler.InputDuration.TotalMilliseconds, delta: 4);
// 0 - 1 - 2 - 33 ...
Assert.IsTrue(scaler.PatternIs(0, 1, 2, 3, 3));
}
[TestMethod]
public void Scale_from_30_to_15()
{
var scaler = new FramerateScaler<string>(30, 15, _originalFrames);
Assert.AreEqual(150, scaler.Count());
Assert.AreEqual(scaler.OutputDuration, scaler.InputDuration);
Assert.IsTrue(_originalFrames
.Where((item, index) => index % 2 == 1)
.SequenceEqual(scaler));
}
[TestMethod]
public void Scale_from_30_to_15_offset_by_half()
{
var scaler = new FramerateScaler<string>(30, 15, _originalFrames, 150);
Assert.AreEqual(75, scaler.Count());
Assert.AreEqual(scaler.OutputDuration, scaler.InputDuration);
Assert.IsTrue(_originalFrames
.Skip(150)
.Where((item, index) => index % 2 == 1)
.SequenceEqual(scaler));
}
}
static class Extensions
{
public static bool PatternIs<T>(this IEnumerable<T> source, params int[] pattern)
{
foreach (var chunk in source.Chunkify(pattern.Length))
{
for (var i = 0; i < chunk.Length; i++)
if (!chunk.ElementAt(i).Equals(
chunk.Distinct().ElementAt(pattern[i])))
return false;
}
return true;
}
// http://stackoverflow.com/a/3210961/3191599
public static IEnumerable<T[]> Chunkify<T>(this IEnumerable<T> source, int size)
{
if (source == null) throw new ArgumentNullException("source");
if (size < 1) throw new ArgumentOutOfRangeException("size");
using (var iter = source.GetEnumerator())
{
while (iter.MoveNext())
{
var chunk = new T[size];
chunk[0] = iter.Current;
for (int i = 1; i < size && iter.MoveNext(); i++)
{
chunk[i] = iter.Current;
}
yield return chunk;
}
}
}
}
只是讓我明白了 - 你想跳過* *幀,這樣它匹配你想要的時間?或者......在重讀時,也許情況正好相反:複製幀? –
'輸出速度'定義您如何跳過*或重複幀。 「持續時間」是*結束條件*。從「開始幀索引」開始,當targetFrames中的幀總數大於「持續時間/輸出速度」時停止。 FPS很棘手,對於'30 fps'中的'100 fps',你必須輸出每幀3.333333(3)'次(有時意味着4幀)。要檢查它是否是'4',再次計算當前時間並添加30 fps,看它是否超過1秒。 – Sinatr
是的。如果原始FPS低於目標,我們將重複幀。否則,我們將跳過它們。 –