Skip to content

Commit a802096

Browse files
committed
feat: add audio sample rate adjustment to built-in presets
Refs: ARC-11095
1 parent 3e182d9 commit a802096

8 files changed

Lines changed: 195 additions & 38 deletions

File tree

CHANGELOG

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
== 8.1.0-beta.4 2025-10-10
2+
3+
Improvements:
4+
* Added audio sample rate adjustment to built-in presets.
5+
* Audio sample rates now work similarly to frame rates - when the input has a lower sample rate than the target, the closest standard sample rate is used.
6+
* Standard audio sample rates supported: 8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000 Hz.
7+
18
== 8.1.0-beta.3 2025-09-30
29

310
Improvements:

lib/ffmpeg/command_args.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ module FFMPEG
1616
# args.to_s # "-map 0:v:0 -c:v:0 libx264 -r 30"
1717
class CommandArgs < RawCommandArgs
1818
STANDARD_FRAME_RATES = [12, 24, 25, 30, 50, 60, 90, 120, 240].freeze
19+
STANDARD_AUDIO_SAMPLE_RATES = [
20+
8000, 11_025, 16_000, 22_050, 32_000, 44_100,
21+
48_000, 88_200, 96_000, 176_400, 192_000
22+
].freeze
1923

2024
class << self
2125
# Composes a new instance of CommandArgs with the given media.
@@ -95,12 +99,23 @@ def audio_bit_rate(target_value, **kwargs)
9599
super(adjusted_audio_bit_rate(target_value), **kwargs)
96100
end
97101

102+
# Sets the audio sample rate to the minimum of the current audio sample rate and the target value.
103+
#
104+
# @param target_value [Integer] The target sample rate.
105+
# @return [self]
106+
def audio_sample_rate(target_value)
107+
return self if target_value.nil?
108+
109+
super(adjusted_audio_sample_rate(target_value))
110+
end
111+
98112
# Returns the minimum of the current frame rate and the target value.
99113
#
100114
# @param target_value [Integer, Float] The target frame rate.
101115
# @return [Numeric]
102116
def adjusted_frame_rate(target_value)
103117
return target_value if media.frame_rate.nil?
118+
return target_value if media.frame_rate <= 0
104119
return target_value if media.frame_rate > target_value
105120

106121
STANDARD_FRAME_RATES.min_by { (_1 - media.frame_rate).abs }
@@ -126,6 +141,20 @@ def adjusted_audio_bit_rate(target_value)
126141
min_bit_rate(media.audio_bit_rate, target_value)
127142
end
128143

144+
# Returns the minimum of the current audio sample rate and the target value.
145+
# Returns the target value if the current sample rate is nil or zero/negative.
146+
# If the media sample rate is lower than the target, returns the closest standard sample rate.
147+
#
148+
# @param target_value [Integer] The target audio sample rate.
149+
# @return [Integer]
150+
def adjusted_audio_sample_rate(target_value)
151+
return target_value if media.audio_sample_rate.nil?
152+
return target_value if media.audio_sample_rate <= 0
153+
return target_value if media.audio_sample_rate > target_value
154+
155+
STANDARD_AUDIO_SAMPLE_RATES.min_by { (_1 - media.audio_sample_rate).abs }
156+
end
157+
129158
private
130159

131160
def min_bit_rate(*values)

lib/ffmpeg/presets/aac.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ def aac_128k(
1212
filename: '%<basename>s.m4a',
1313
metadata: nil,
1414
threads: FFMPEG.threads,
15+
audio_sample_rate: 48_000,
1516
&
1617
)
1718
AAC.new(
1819
name:,
1920
filename:,
2021
metadata:,
2122
threads:,
23+
audio_sample_rate:,
2224
audio_bit_rate: '128k',
2325
&
2426
)
@@ -29,13 +31,15 @@ def aac_192k(
2931
filename: '%<basename>s.m4a',
3032
metadata: nil,
3133
threads: FFMPEG.threads,
34+
audio_sample_rate: 48_000,
3235
&
3336
)
3437
AAC.new(
3538
name:,
3639
filename:,
3740
metadata:,
3841
threads:,
42+
audio_sample_rate:,
3943
audio_bit_rate: '192k',
4044
&
4145
)
@@ -46,13 +50,15 @@ def aac_320k(
4650
filename: '%<basename>s.m4a',
4751
metadata: nil,
4852
threads: FFMPEG.threads,
53+
audio_sample_rate: 48_000,
4954
&
5055
)
5156
AAC.new(
5257
name:,
5358
filename:,
5459
metadata:,
5560
threads:,
61+
audio_sample_rate:,
5662
audio_bit_rate: '320k',
5763
&
5864
)
@@ -61,7 +67,7 @@ def aac_320k(
6167

6268
# Preset to encode AAC audio files.
6369
class AAC < Preset
64-
attr_reader :threads, :audio_bit_rate
70+
attr_reader :threads, :audio_bit_rate, :audio_sample_rate
6571

6672
# @param name [String] The name of the preset.
6773
# @param filename [String] The filename format of the output.
@@ -74,10 +80,12 @@ def initialize(
7480
metadata: nil,
7581
threads: FFMPEG.threads,
7682
audio_bit_rate: '128k',
83+
audio_sample_rate: 48_000,
7784
&
7885
)
7986
@threads = threads
8087
@audio_bit_rate = audio_bit_rate
88+
@audio_sample_rate = audio_sample_rate
8189
preset = self
8290

8391
super(name:, filename:, metadata:) do
@@ -92,6 +100,7 @@ def initialize(
92100

93101
map media.audio_mapping_id do
94102
audio_bit_rate preset.audio_bit_rate
103+
audio_sample_rate preset.audio_sample_rate
95104
end
96105
end
97106
end

lib/ffmpeg/presets/dash/aac.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ def aac_128k(
1414
metadata: nil,
1515
threads: FFMPEG.threads,
1616
segment_duration: 4,
17+
audio_sample_rate: 48_000,
1718
&
1819
)
1920
AAC.new(
@@ -22,6 +23,7 @@ def aac_128k(
2223
metadata:,
2324
threads:,
2425
segment_duration:,
26+
audio_sample_rate:,
2527
audio_bit_rate: '128k',
2628
&
2729
)
@@ -33,6 +35,7 @@ def aac_192k(
3335
metadata: nil,
3436
threads: FFMPEG.threads,
3537
segment_duration: 4,
38+
audio_sample_rate: 48_000,
3639
&
3740
)
3841
AAC.new(
@@ -41,6 +44,7 @@ def aac_192k(
4144
metadata:,
4245
threads:,
4346
segment_duration:,
47+
audio_sample_rate:,
4448
audio_bit_rate: '192k',
4549
&
4650
)
@@ -52,6 +56,7 @@ def aac_320k(
5256
metadata: nil,
5357
threads: FFMPEG.threads,
5458
segment_duration: 4,
59+
audio_sample_rate: 48_000,
5560
&
5661
)
5762
AAC.new(
@@ -60,6 +65,7 @@ def aac_320k(
6065
metadata:,
6166
threads:,
6267
segment_duration:,
68+
audio_sample_rate:,
6369
audio_bit_rate: '320k',
6470
&
6571
)
@@ -68,7 +74,7 @@ def aac_320k(
6874

6975
# Preset to encode DASH AAC audio files.
7076
class AAC < DASH
71-
attr_reader :audio_bit_rate
77+
attr_reader :audio_bit_rate, :audio_sample_rate
7278

7379
# @param name [String] The name of the preset.
7480
# @param filename [String] The filename format of the output.
@@ -82,9 +88,11 @@ def initialize(
8288
threads: FFMPEG.threads,
8389
segment_duration: 4,
8490
audio_bit_rate: '128k',
91+
audio_sample_rate: 48_000,
8592
&
8693
)
8794
@audio_bit_rate = audio_bit_rate
95+
@audio_sample_rate = audio_sample_rate
8896
preset = self
8997

9098
super(
@@ -102,6 +110,7 @@ def initialize(
102110

103111
map media.audio_mapping_id do
104112
audio_bit_rate preset.audio_bit_rate
113+
audio_sample_rate preset.audio_sample_rate
105114
end
106115
end
107116
end

lib/ffmpeg/presets/dash/h264.rb

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def h264_360p(
2020
segment_duration: 4,
2121
keyframe_interval: 2,
2222
audio_bit_rate: '128k',
23+
audio_sample_rate: 48_000,
2324
frame_rate: 30,
2425
ld_frame_rate: 24,
2526
&
@@ -32,11 +33,11 @@ def h264_360p(
3233
segment_duration:,
3334
keyframe_interval:,
3435
h264_presets: [
35-
Presets.h264_360p(audio_bit_rate:, frame_rate:)
36+
Presets.h264_360p(audio_bit_rate:, audio_sample_rate:, frame_rate:)
3637
],
3738
ld_h264_presets: [
38-
Presets.h264_240p(audio_bit_rate:, frame_rate: ld_frame_rate),
39-
Presets.h264_144p(audio_bit_rate:, frame_rate: ld_frame_rate)
39+
Presets.h264_240p(audio_bit_rate:, audio_sample_rate:, frame_rate: ld_frame_rate),
40+
Presets.h264_144p(audio_bit_rate:, audio_sample_rate:, frame_rate: ld_frame_rate)
4041
],
4142
&
4243
)
@@ -50,6 +51,7 @@ def h264_480p(
5051
segment_duration: 4,
5152
keyframe_interval: 2,
5253
audio_bit_rate: '128k',
54+
audio_sample_rate: 48_000,
5355
frame_rate: 30,
5456
ld_frame_rate: 24,
5557
&
@@ -62,12 +64,12 @@ def h264_480p(
6264
segment_duration:,
6365
keyframe_interval:,
6466
h264_presets: [
65-
Presets.h264_480p(audio_bit_rate:, frame_rate:),
66-
Presets.h264_360p(audio_bit_rate:, frame_rate:)
67+
Presets.h264_480p(audio_bit_rate:, audio_sample_rate:, frame_rate:),
68+
Presets.h264_360p(audio_bit_rate:, audio_sample_rate:, frame_rate:)
6769
],
6870
ld_h264_presets: [
69-
Presets.h264_240p(audio_bit_rate:, frame_rate: ld_frame_rate),
70-
Presets.h264_144p(audio_bit_rate:, frame_rate: ld_frame_rate)
71+
Presets.h264_240p(audio_bit_rate:, audio_sample_rate:, frame_rate: ld_frame_rate),
72+
Presets.h264_144p(audio_bit_rate:, audio_sample_rate:, frame_rate: ld_frame_rate)
7173
],
7274
&
7375
)
@@ -81,6 +83,7 @@ def h264_720p(
8183
segment_duration: 4,
8284
keyframe_interval: 2,
8385
audio_bit_rate: '128k',
86+
audio_sample_rate: 48_000,
8487
ld_frame_rate: 24,
8588
sd_frame_rate: 30,
8689
hd_frame_rate: 30,
@@ -94,13 +97,13 @@ def h264_720p(
9497
keyframe_interval:,
9598
segment_duration:,
9699
h264_presets: [
97-
Presets.h264_720p(audio_bit_rate:, frame_rate: hd_frame_rate),
98-
Presets.h264_480p(audio_bit_rate:, frame_rate: sd_frame_rate),
99-
Presets.h264_360p(audio_bit_rate:, frame_rate: sd_frame_rate)
100+
Presets.h264_720p(audio_bit_rate:, audio_sample_rate:, frame_rate: hd_frame_rate),
101+
Presets.h264_480p(audio_bit_rate:, audio_sample_rate:, frame_rate: sd_frame_rate),
102+
Presets.h264_360p(audio_bit_rate:, audio_sample_rate:, frame_rate: sd_frame_rate)
100103
],
101104
ld_h264_presets: [
102-
Presets.h264_240p(audio_bit_rate:, frame_rate: ld_frame_rate),
103-
Presets.h264_144p(audio_bit_rate:, frame_rate: ld_frame_rate)
105+
Presets.h264_240p(audio_bit_rate:, audio_sample_rate:, frame_rate: ld_frame_rate),
106+
Presets.h264_144p(audio_bit_rate:, audio_sample_rate:, frame_rate: ld_frame_rate)
104107
],
105108
&
106109
)
@@ -114,6 +117,7 @@ def h264_1080p(
114117
segment_duration: 4,
115118
keyframe_interval: 2,
116119
audio_bit_rate: '128k',
120+
audio_sample_rate: 48_000,
117121
ld_frame_rate: 24,
118122
sd_frame_rate: 30,
119123
hd_frame_rate: 30,
@@ -127,14 +131,14 @@ def h264_1080p(
127131
keyframe_interval:,
128132
segment_duration:,
129133
h264_presets: [
130-
Presets.h264_1080p(audio_bit_rate:, frame_rate: hd_frame_rate),
131-
Presets.h264_720p(audio_bit_rate:, frame_rate: hd_frame_rate),
132-
Presets.h264_480p(audio_bit_rate:, frame_rate: sd_frame_rate),
133-
Presets.h264_360p(audio_bit_rate:, frame_rate: sd_frame_rate)
134+
Presets.h264_1080p(audio_bit_rate:, audio_sample_rate:, frame_rate: hd_frame_rate),
135+
Presets.h264_720p(audio_bit_rate:, audio_sample_rate:, frame_rate: hd_frame_rate),
136+
Presets.h264_480p(audio_bit_rate:, audio_sample_rate:, frame_rate: sd_frame_rate),
137+
Presets.h264_360p(audio_bit_rate:, audio_sample_rate:, frame_rate: sd_frame_rate)
134138
],
135139
ld_h264_presets: [
136-
Presets.h264_240p(audio_bit_rate:, frame_rate: ld_frame_rate),
137-
Presets.h264_144p(audio_bit_rate:, frame_rate: ld_frame_rate)
140+
Presets.h264_240p(audio_bit_rate:, audio_sample_rate:, frame_rate: ld_frame_rate),
141+
Presets.h264_144p(audio_bit_rate:, audio_sample_rate:, frame_rate: ld_frame_rate)
138142
],
139143
&
140144
)
@@ -148,6 +152,7 @@ def h264_1440p(
148152
segment_duration: 4,
149153
keyframe_interval: 2,
150154
audio_bit_rate: '128k',
155+
audio_sample_rate: 48_000,
151156
ld_frame_rate: 24,
152157
sd_frame_rate: 30,
153158
hd_frame_rate: 30,
@@ -161,15 +166,15 @@ def h264_1440p(
161166
keyframe_interval:,
162167
segment_duration:,
163168
h264_presets: [
164-
Presets.h264_1440p(audio_bit_rate:, frame_rate: hd_frame_rate),
165-
Presets.h264_1080p(audio_bit_rate:, frame_rate: hd_frame_rate),
166-
Presets.h264_720p(audio_bit_rate:, frame_rate: hd_frame_rate),
167-
Presets.h264_480p(audio_bit_rate:, frame_rate: sd_frame_rate),
168-
Presets.h264_360p(audio_bit_rate:, frame_rate: sd_frame_rate)
169+
Presets.h264_1440p(audio_bit_rate:, audio_sample_rate:, frame_rate: hd_frame_rate),
170+
Presets.h264_1080p(audio_bit_rate:, audio_sample_rate:, frame_rate: hd_frame_rate),
171+
Presets.h264_720p(audio_bit_rate:, audio_sample_rate:, frame_rate: hd_frame_rate),
172+
Presets.h264_480p(audio_bit_rate:, audio_sample_rate:, frame_rate: sd_frame_rate),
173+
Presets.h264_360p(audio_bit_rate:, audio_sample_rate:, frame_rate: sd_frame_rate)
169174
],
170175
ld_h264_presets: [
171-
Presets.h264_240p(audio_bit_rate:, frame_rate: ld_frame_rate),
172-
Presets.h264_144p(audio_bit_rate:, frame_rate: ld_frame_rate)
176+
Presets.h264_240p(audio_bit_rate:, audio_sample_rate:, frame_rate: ld_frame_rate),
177+
Presets.h264_144p(audio_bit_rate:, audio_sample_rate:, frame_rate: ld_frame_rate)
173178
],
174179
&
175180
)
@@ -183,6 +188,7 @@ def h264_4k(
183188
segment_duration: 4,
184189
keyframe_interval: 2,
185190
audio_bit_rate: '128k',
191+
audio_sample_rate: 48_000,
186192
ld_frame_rate: 24,
187193
sd_frame_rate: 30,
188194
hd_frame_rate: 60,
@@ -197,16 +203,16 @@ def h264_4k(
197203
segment_duration:,
198204
keyframe_interval:,
199205
h264_presets: [
200-
Presets.h264_4k(audio_bit_rate:, frame_rate: uhd_frame_rate),
201-
Presets.h264_1440p(audio_bit_rate:, frame_rate: hd_frame_rate),
202-
Presets.h264_1080p(audio_bit_rate:, frame_rate: hd_frame_rate),
203-
Presets.h264_720p(audio_bit_rate:, frame_rate: hd_frame_rate),
204-
Presets.h264_480p(audio_bit_rate:, frame_rate: sd_frame_rate),
205-
Presets.h264_360p(audio_bit_rate:, frame_rate: sd_frame_rate)
206+
Presets.h264_4k(audio_bit_rate:, audio_sample_rate:, frame_rate: uhd_frame_rate),
207+
Presets.h264_1440p(audio_bit_rate:, audio_sample_rate:, frame_rate: hd_frame_rate),
208+
Presets.h264_1080p(audio_bit_rate:, audio_sample_rate:, frame_rate: hd_frame_rate),
209+
Presets.h264_720p(audio_bit_rate:, audio_sample_rate:, frame_rate: hd_frame_rate),
210+
Presets.h264_480p(audio_bit_rate:, audio_sample_rate:, frame_rate: sd_frame_rate),
211+
Presets.h264_360p(audio_bit_rate:, audio_sample_rate:, frame_rate: sd_frame_rate)
206212
],
207213
ld_h264_presets: [
208-
Presets.h264_240p(audio_bit_rate:, frame_rate: ld_frame_rate),
209-
Presets.h264_144p(audio_bit_rate:, frame_rate: ld_frame_rate)
214+
Presets.h264_240p(audio_bit_rate:, audio_sample_rate:, frame_rate: ld_frame_rate),
215+
Presets.h264_144p(audio_bit_rate:, audio_sample_rate:, frame_rate: ld_frame_rate)
210216
],
211217
&
212218
)
@@ -305,6 +311,7 @@ def initialize(
305311
# Reset the audio stream's timestamps to start from 0.
306312
filter Filter.new(:audio, 'asetpts', expr: 'PTS-STARTPTS')
307313
audio_bit_rate h264_presets.first.audio_bit_rate
314+
audio_sample_rate h264_presets.first.audio_sample_rate
308315
end
309316
end
310317
end

0 commit comments

Comments
 (0)