Skip to content

Commit 2f207da

Browse files
author
RandomEngy
committed
Changed video bitrate calculation to use HandbrakeInterop rather than the core handbrake function, since hb_calc_bitrate is going away soon. Also added a function to predict file size and cleaned up control visibility for different video rate types.
1 parent 745265f commit 2f207da

10 files changed

Lines changed: 344 additions & 46 deletions

File tree

HandBrakeInterop/HandBrakeInterop/HandBrakeInstance.cs

Lines changed: 113 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public class HandBrakeInstance : IDisposable
8080
/// <summary>
8181
/// The current encode job for this instance.
8282
/// </summary>
83-
private EncodeJob job;
83+
private EncodeJob currentJob;
8484

8585
/// <summary>
8686
/// True if the current job is scanning for subtitles.
@@ -288,16 +288,80 @@ public BitmapImage GetPreview(EncodeJob job, int previewNumber)
288288
/// <returns>The video bitrate in kbps.</returns>
289289
public int CalculateBitrate(EncodeJob job, int sizeMB)
290290
{
291-
hb_title_s title = this.GetOriginalTitle(job.Title);
291+
long availableBytes = sizeMB * 1024 * 1024;
292292

293-
hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(title.job);
294-
List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job);
293+
EncodingProfile profile = job.EncodingProfile;
294+
Title title = this.GetTitle(job.Title);
295295

296-
int calculatedBitrate = HbLib.hb_calc_bitrate(ref nativeJob, sizeMB);
296+
double lengthSeconds = HandBrakeUtils.GetJobLengthSeconds(job, title);
297+
lengthSeconds += 1.5;
297298

298-
InteropUtilities.FreeMemory(allocatedMemory);
299+
double outputFramerate;
300+
if (profile.Framerate == 0)
301+
{
302+
outputFramerate = title.Framerate;
303+
}
304+
else
305+
{
306+
// Not sure what to do for VFR here hb_calc_bitrate never handled it...
307+
// just use the peak for now.
308+
outputFramerate = profile.Framerate;
309+
}
310+
311+
long frames = (long)(lengthSeconds * outputFramerate);
312+
313+
availableBytes -= frames * HandBrakeUtils.ContainerOverheadPerFrame;
314+
315+
List<Tuple<AudioEncoding, int>> outputTrackList = this.GetOutputTracks(job);
316+
availableBytes -= HandBrakeUtils.GetAudioSize(job, lengthSeconds, title, outputTrackList);
317+
318+
if (availableBytes < 0)
319+
{
320+
return 0;
321+
}
299322

300-
return calculatedBitrate;
323+
// Video bitrate is in kilobits per second, or where 1 kbps is 1000 bits per second.
324+
// So 1 kbps is 125 bytes per second.
325+
return (int)(availableBytes / (125 * lengthSeconds));
326+
}
327+
328+
/// <summary>
329+
/// Gives estimated file size (in MB) of the given job and video bitrate.
330+
/// </summary>
331+
/// <param name="job">The encode job.</param>
332+
/// <param name="videoBitrate">The video bitrate to be used (kbps).</param>
333+
/// <returns>The estimated file size (in MB) of the given job and video bitrate.</returns>
334+
public double CalculateFileSize(EncodeJob job, int videoBitrate)
335+
{
336+
long totalBytes = 0;
337+
338+
EncodingProfile profile = job.EncodingProfile;
339+
Title title = this.GetTitle(job.Title);
340+
341+
double lengthSeconds = HandBrakeUtils.GetJobLengthSeconds(job, title);
342+
lengthSeconds += 1.5;
343+
344+
double outputFramerate;
345+
if (profile.Framerate == 0)
346+
{
347+
outputFramerate = title.Framerate;
348+
}
349+
else
350+
{
351+
// Not sure what to do for VFR here hb_calc_bitrate never handled it...
352+
// just use the peak for now.
353+
outputFramerate = profile.Framerate;
354+
}
355+
356+
long frames = (long)(lengthSeconds * outputFramerate);
357+
358+
totalBytes += (long)(lengthSeconds * videoBitrate * 125);
359+
totalBytes += frames * HandBrakeUtils.ContainerOverheadPerFrame;
360+
361+
List<Tuple<AudioEncoding, int>> outputTrackList = this.GetOutputTracks(job);
362+
totalBytes += HandBrakeUtils.GetAudioSize(job, lengthSeconds, title, outputTrackList);
363+
364+
return (double)totalBytes / 1024 / 1024;
301365
}
302366

303367
/// <summary>
@@ -318,7 +382,7 @@ public void StartEncode(EncodeJob jobToStart)
318382
/// <param name="previewSeconds">The number of seconds in the preview.</param>
319383
public void StartEncode(EncodeJob job, bool preview, int previewNumber, int previewSeconds)
320384
{
321-
this.job = job;
385+
this.currentJob = job;
322386
hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);
323387
this.encodeAllocatedMemory = this.ApplyJob(ref nativeJob, job, preview, previewNumber, previewSeconds);
324388

@@ -484,7 +548,7 @@ public void GetSize(EncodeJob job, out int width, out int height, out int parWid
484548
return;
485549
}
486550

487-
hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);
551+
var nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);
488552
List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job, false, 0, 0);
489553

490554
int refWidth = 0;
@@ -541,7 +605,7 @@ protected virtual void Dispose(bool disposing)
541605
/// </summary>
542606
private void PollScanProgress()
543607
{
544-
hb_state_s state = new hb_state_s();
608+
var state = new hb_state_s();
545609
HbLib.hb_get_state(this.hbHandle, ref state);
546610

547611
if (state.state == NativeConstants.HB_STATE_SCANNING)
@@ -568,7 +632,7 @@ private void PollScanProgress()
568632

569633
if (this.originalTitles.Count > 0)
570634
{
571-
hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.originalTitles[0].job);
635+
var nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.originalTitles[0].job);
572636
this.featureTitle = nativeJob.feature;
573637
}
574638
else
@@ -600,7 +664,7 @@ private void PollEncodeProgress()
600664
int pass = 1;
601665
int rawJobNumber = state.param.working.job_cur;
602666

603-
if (this.job.EncodingProfile.TwoPass)
667+
if (this.currentJob.EncodingProfile.TwoPass)
604668
{
605669
if (this.subtitleScan)
606670
{
@@ -995,25 +1059,12 @@ private List<IntPtr> ApplyJob(ref hb_job_s nativeJob, EncodeJob job, bool previe
9951059

9961060
var audioList = new List<hb_audio_s>();
9971061
int numTracks = 0;
998-
foreach (AudioEncoding encoding in profile.AudioEncodings)
1062+
1063+
List<Tuple<AudioEncoding, int>> outputTrackList = this.GetOutputTracks(job);
1064+
1065+
foreach (Tuple<AudioEncoding, int> outputTrack in outputTrackList)
9991066
{
1000-
if (encoding.InputNumber == 0)
1001-
{
1002-
// Add this encoding for all chosen tracks
1003-
foreach (int chosenTrack in job.ChosenAudioTracks)
1004-
{
1005-
if (titleAudio.Count >= chosenTrack)
1006-
{
1007-
audioList.Add(ConvertAudioBack(encoding, titleAudio[chosenTrack - 1], chosenTrack, numTracks++, allocatedMemory));
1008-
}
1009-
}
1010-
}
1011-
else if (encoding.InputNumber <= job.ChosenAudioTracks.Count)
1012-
{
1013-
// Add this encoding for the specified track, if it exists
1014-
int trackNumber = job.ChosenAudioTracks[encoding.InputNumber - 1];
1015-
audioList.Add(ConvertAudioBack(encoding, titleAudio[trackNumber - 1], trackNumber, numTracks++, allocatedMemory));
1016-
}
1067+
audioList.Add(ConvertAudioBack(outputTrack.Item1, titleAudio[outputTrack.Item2 -1], outputTrack.Item2, numTracks++, allocatedMemory));
10171068
}
10181069

10191070
NativeList nativeAudioList = InteropUtilities.ConvertListBack<hb_audio_s>(audioList);
@@ -1152,6 +1203,36 @@ private List<IntPtr> ApplyJob(ref hb_job_s nativeJob, EncodeJob job, bool previe
11521203
return allocatedMemory;
11531204
}
11541205

1206+
/// <summary>
1207+
/// Gets a list of encodings and target track indices (1-based).
1208+
/// </summary>
1209+
/// <param name="job">The encode job</param>
1210+
/// <returns>A list of encodings and target track indices (1-based).</returns>
1211+
private List<Tuple<AudioEncoding, int>> GetOutputTracks(EncodeJob job)
1212+
{
1213+
var list = new List<Tuple<AudioEncoding, int>>();
1214+
1215+
foreach (AudioEncoding encoding in job.EncodingProfile.AudioEncodings)
1216+
{
1217+
if (encoding.InputNumber == 0)
1218+
{
1219+
// Add this encoding for all chosen tracks
1220+
foreach (int chosenTrack in job.ChosenAudioTracks)
1221+
{
1222+
list.Add(new Tuple<AudioEncoding, int>(encoding, chosenTrack));
1223+
}
1224+
}
1225+
else if (encoding.InputNumber <= job.ChosenAudioTracks.Count)
1226+
{
1227+
// Add this encoding for the specified track, if it exists
1228+
int trackNumber = job.ChosenAudioTracks[encoding.InputNumber - 1];
1229+
list.Add(new Tuple<AudioEncoding, int>(encoding, trackNumber));
1230+
}
1231+
}
1232+
1233+
return list;
1234+
}
1235+
11551236
/// <summary>
11561237
/// Adds a filter to the given filter list.
11571238
/// </summary>
@@ -1373,7 +1454,8 @@ private Title ConvertTitle(hb_title_s title)
13731454
LanguageCode = audio.config.lang.iso639_2,
13741455
Description = audio.config.lang.description,
13751456
ChannelLayout = audio.config.input.channel_layout,
1376-
SampleRate = audio.config.input.samplerate
1457+
SampleRate = audio.config.input.samplerate,
1458+
Bitrate = audio.config.input.bitrate
13771459
};
13781460

13791461
newTitle.AudioTracks.Add(newAudio);

HandBrakeInterop/HandBrakeInterop/HandBrakeUtils.cs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Text;
5+
using HandBrake.SourceData;
56

67
namespace HandBrake.Interop
78
{
89
public static class HandBrakeUtils
910
{
11+
/// <summary>
12+
/// Estimated overhead in bytes for each frame in output container.
13+
/// </summary>
14+
internal const int ContainerOverheadPerFrame = 6;
15+
1016
/// <summary>
1117
/// The callback for log messages from HandBrake.
1218
/// </summary>
@@ -138,5 +144,100 @@ public static int SanitizeAudioBitrate(int audioBitrate, AudioEncoder encoder, i
138144
{
139145
return HbLib.hb_get_best_audio_bitrate(Converters.AudioEncoderToNative(encoder), audioBitrate, sampleRate, Converters.MixdownToNative(mixdown));
140146
}
147+
148+
/// <summary>
149+
/// Gets the total number of seconds on the given encode job.
150+
/// </summary>
151+
/// <param name="job">The encode job to query.</param>
152+
/// <param name="title">The title being encoded.</param>
153+
/// <returns>The total number of seconds of video to encode.</returns>
154+
internal static double GetJobLengthSeconds(EncodeJob job, Title title)
155+
{
156+
switch (job.RangeType)
157+
{
158+
case VideoRangeType.Chapters:
159+
TimeSpan duration = TimeSpan.Zero;
160+
for (int i = job.ChapterStart; i <= job.ChapterEnd; i++)
161+
{
162+
duration += title.Chapters[i - 1].Duration;
163+
}
164+
165+
return duration.TotalSeconds;
166+
case VideoRangeType.Seconds:
167+
return job.SecondsEnd - job.SecondsStart;
168+
case VideoRangeType.Frames:
169+
return (job.FramesEnd - job.FramesStart) / title.Framerate;
170+
}
171+
172+
return 0;
173+
}
174+
175+
/// <summary>
176+
/// Gets the number of audio samples used per frame for the given audio encoder.
177+
/// </summary>
178+
/// <param name="encoder">The encoder to query.</param>
179+
/// <returns>The number of audio samples used per frame for the given
180+
/// audio encoder.</returns>
181+
internal static int GetAudioSamplesPerFrame(AudioEncoder encoder)
182+
{
183+
switch (encoder)
184+
{
185+
case AudioEncoder.Faac:
186+
case AudioEncoder.Vorbis:
187+
return 1024;
188+
case AudioEncoder.Lame:
189+
return 1152;
190+
case AudioEncoder.Ac3:
191+
case AudioEncoder.Passthrough:
192+
case AudioEncoder.Ac3Passthrough:
193+
case AudioEncoder.DtsPassthrough:
194+
return 1536;
195+
}
196+
197+
System.Diagnostics.Debug.Assert(true, "Audio encoder unrecognized.");
198+
return 0;
199+
}
200+
201+
/// <summary>
202+
/// Gets the size in bytes for the audio with the given parameters.
203+
/// </summary>
204+
/// <param name="job">The encode job.</param>
205+
/// <param name="lengthSeconds">The length of the encode in seconds.</param>
206+
/// <param name="title">The title to encode.</param>
207+
/// <param name="outputTrackList">The list of tracks to encode.</param>
208+
/// <returns>The size in bytes for the audio with the given parameters.</returns>
209+
internal static long GetAudioSize(EncodeJob job, double lengthSeconds, Title title, List<Tuple<AudioEncoding, int>> outputTrackList)
210+
{
211+
long audioBytes = 0;
212+
213+
foreach (Tuple<AudioEncoding, int> outputTrack in outputTrackList)
214+
{
215+
AudioEncoding encoding = outputTrack.Item1;
216+
AudioTrack track = title.AudioTracks[outputTrack.Item2 - 1];
217+
218+
int samplesPerFrame = HandBrakeUtils.GetAudioSamplesPerFrame(encoding.Encoder);
219+
int audioBitrate;
220+
221+
if (encoding.Encoder == AudioEncoder.Passthrough ||
222+
encoding.Encoder == AudioEncoder.Ac3Passthrough ||
223+
encoding.Encoder == AudioEncoder.DtsPassthrough)
224+
{
225+
// Input bitrate is in bits/second.
226+
audioBitrate = track.Bitrate / 8;
227+
}
228+
else
229+
{
230+
// Output bitrate is in kbps.
231+
audioBitrate = encoding.Bitrate * 1000 / 8;
232+
}
233+
234+
audioBytes += (long)(lengthSeconds * audioBitrate);
235+
236+
// Audio overhead
237+
audioBytes += encoding.SampleRateRaw * ContainerOverheadPerFrame / samplesPerFrame;
238+
}
239+
240+
return audioBytes;
241+
}
141242
}
142243
}

HandBrakeInterop/HandBrakeInterop/Model/Encoding/AudioEncoding.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ public class AudioEncoding
99
{
1010
public int InputNumber { get; set; }
1111
public AudioEncoder Encoder { get; set; }
12+
13+
/// <summary>
14+
/// Gets or sets the bitrate (in kbps) of this track.
15+
/// </summary>
1216
public int Bitrate { get; set; }
1317
public Mixdown Mixdown { get; set; }
1418

HandBrakeInterop/HandBrakeInterop/SourceData/AudioTrack.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class AudioTrack
4646
public int SampleRate { get; set; }
4747

4848
/// <summary>
49-
/// The bitrate (in kbps) of this Audio Track
49+
/// The bitrate (in bits/sec) of this Audio Track.
5050
/// </summary>
5151
public int Bitrate { get; set; }
5252

VidCoder/App.xaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<converters:VisibilityConverter x:Key="VisibilityConverter" />
77
<converters:EnumBoolConverter x:Key="EnumBoolConverter" />
88
<converters:EnumBoolInverseConverter x:Key="EnumBoolInverseConverter" />
9+
<converters:EnumVisibilityConverter x:Key="EnumVisibilityConverter" />
10+
<converters:EnumVisibilityInverseConverter x:Key="EnumVisibilityInverseConverter" />
911
<converters:InverseBoolConverter x:Key="InverseBoolConverter" />
1012
<converters:PercentDisplayConverter x:Key="PercentDisplayConverter" />
1113
<converters:DoubleDisplayConverter x:Key="DoubleDisplayConverter" />
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Windows;
7+
using System.Windows.Data;
8+
9+
namespace VidCoder.Converters
10+
{
11+
public class EnumVisibilityConverter : IValueConverter
12+
{
13+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
14+
{
15+
return string.Compare(value.ToString(), (string)parameter, StringComparison.InvariantCultureIgnoreCase) == 0 ? Visibility.Visible : Visibility.Collapsed;
16+
}
17+
18+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
19+
{
20+
return null;
21+
}
22+
}
23+
}

0 commit comments

Comments
 (0)