mp3lame_encoder/
lib.rs

1//!High level wrapper over [mp3lame-sys](https://crates.io/crates/mp3lame-sys)
2//!
3//!## Example
4//!
5//!```rust
6//!use mp3lame_encoder::{Builder, Id3Tag, DualPcm, FlushNoGap};
7//!
8//!let mut mp3_encoder = Builder::new().expect("Create LAME builder");
9//!mp3_encoder.set_num_channels(2).expect("set channels");
10//!mp3_encoder.set_sample_rate(44_100).expect("set sample rate");
11//!mp3_encoder.set_brate(mp3lame_encoder::Bitrate::Kbps192).expect("set brate");
12//!mp3_encoder.set_quality(mp3lame_encoder::Quality::Best).expect("set quality");
13//!mp3_encoder.set_id3_tag(Id3Tag {
14//!    title: b"My title",
15//!    artist: &[],
16//!    album: b"My album",
17//!    album_art: &[],
18//!    year: b"Current year",
19//!    comment: b"Just my comment",
20//!});
21//!let mut mp3_encoder = mp3_encoder.build().expect("To initialize LAME encoder");
22//!
23//!//use actual PCM data
24//!let input = DualPcm {
25//!    left: &[0u16, 0],
26//!    right: &[0u16, 0],
27//!};
28//!
29//!let mut mp3_out_buffer = Vec::new();
30//!mp3_out_buffer.reserve(mp3lame_encoder::max_required_buffer_size(input.left.len()));
31//!let encoded_size = mp3_encoder.encode(input, mp3_out_buffer.spare_capacity_mut()).expect("To encode");
32//!unsafe {
33//!    mp3_out_buffer.set_len(mp3_out_buffer.len().wrapping_add(encoded_size));
34//!}
35//!
36//!let encoded_size = mp3_encoder.flush::<FlushNoGap>(mp3_out_buffer.spare_capacity_mut()).expect("to flush");
37//!unsafe {
38//!    mp3_out_buffer.set_len(mp3_out_buffer.len().wrapping_add(encoded_size));
39//!}
40//!//At this point your mp3_out_buffer should have full MP3 data, ready to be written on file system or whatever
41//!
42//!```
43
44#![no_std]
45#![warn(missing_docs)]
46#![allow(clippy::style)]
47#![allow(clippy::missing_safety_doc)]
48#![cfg_attr(rustfmt, rustfmt_skip)]
49
50#[cfg(feature = "std")]
51extern crate std;
52
53extern crate alloc;
54
55pub use mp3lame_sys as ffi;
56
57use alloc::vec::Vec;
58use core::mem::{self, MaybeUninit};
59use core::ptr::{self, NonNull};
60use core::{cmp, fmt};
61
62mod input;
63pub use input::*;
64
65///Maximum size of album art
66pub const MAX_ALBUM_ART_SIZE: usize = 128 * 1024;
67
68///Calculates maximum required size for specified number of samples.
69///
70///Note that actual requirement may vary depending on encoder parameters,
71///but this size should be generally enough for encoding given number of samples
72pub const fn max_required_buffer_size(sample_number: usize) -> usize {
73    //add 25% sample number + mp3 frame size 7200
74    let mut sample_extra_size = sample_number / 4;
75    if (sample_number % 4) > 0 {
76        sample_extra_size = sample_extra_size.wrapping_add(1);
77    }
78    sample_number.wrapping_add(sample_extra_size).wrapping_add(7200)
79}
80
81#[derive(Debug, Copy, Clone, Eq, PartialEq)]
82///ID3 setter errors
83pub enum Id3TagError {
84    ///Specified buffer exceed limit of 128kb
85    AlbumArtOverflow,
86}
87
88#[derive(Debug, Copy, Clone, Eq, PartialEq)]
89///Encoder builder errors
90pub enum BuildError {
91    ///Generic error, indicates invalid input or state
92    Generic,
93    ///Failed to allocate memory
94    NoMem,
95    ///Invalid brate
96    BadBRate,
97    ///Invalid sample frequency
98    BadSampleFreq,
99    ///Internal error
100    InternalError,
101    ///Other errors, most likely unexpected.
102    Other(libc::c_int),
103}
104
105impl BuildError {
106    #[inline(always)]
107    fn from_c_int(code: libc::c_int) -> Result<(), Self> {
108        if code >= 0 {
109            return Ok(())
110        }
111
112        match code {
113            -1 => Err(Self::Generic),
114            -10 => Err(Self::NoMem),
115            -11 => Err(Self::BadBRate),
116            -12 => Err(Self::BadSampleFreq),
117            -13 => Err(Self::InternalError),
118            _ => Err(Self::Other(code)),
119        }
120    }
121}
122
123#[cfg(feature = "std")]
124impl std::error::Error for BuildError {
125}
126
127impl fmt::Display for BuildError {
128    #[inline]
129    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
130        match self {
131            Self::Generic => fmt.write_str("error"),
132            Self::NoMem => fmt.write_str("alloc failure"),
133            Self::BadBRate => fmt.write_str("bad bitrate"),
134            Self::BadSampleFreq => fmt.write_str("bad sample frequency"),
135            Self::InternalError => fmt.write_str("internal error"),
136            Self::Other(code) => fmt.write_fmt(format_args!("error code={code}")),
137        }
138    }
139}
140
141#[derive(Debug, Copy, Clone, Eq, PartialEq)]
142///Encoder errors
143pub enum EncodeError {
144    ///Indicates output buffer is insufficient.
145    ///
146    ///Consider using [max_required_buffer_size](max_required_buffer_size) to determine required
147    ///space to alloc.
148    BufferTooSmall,
149    ///Failed to allocate memory
150    NoMem,
151    ///Invalid encoder state
152    ///
153    ///Should not happen if encoder created through builder
154    InvalidState,
155    ///Psycho acoustic problems, whatever it means.
156    PsychoAcoustic,
157    ///Other errors, most likely unexpected.
158    Other(libc::c_int),
159}
160
161impl EncodeError {
162    #[inline(always)]
163    fn from_c_int(code: libc::c_int) -> Result<usize, Self> {
164        if code >= 0 {
165            return Ok(code as usize)
166        }
167
168        match code {
169            -1 => Err(Self::BufferTooSmall),
170            -2 => Err(Self::NoMem),
171            -3 => Err(Self::InvalidState),
172            -4 => Err(Self::PsychoAcoustic),
173            _ => Err(Self::Other(code)),
174        }
175    }
176}
177
178#[cfg(feature = "std")]
179impl std::error::Error for EncodeError {
180}
181
182impl fmt::Display for EncodeError {
183    #[inline]
184    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
185        match self {
186            Self::BufferTooSmall => fmt.write_str("output buffer is insufficient for encoder output"),
187            Self::NoMem => fmt.write_str("alloc failure"),
188            Self::InvalidState => fmt.write_str("attempt to use uninitialized encoder"),
189            Self::PsychoAcoustic => fmt.write_str("psycho acoustic problems"),
190            Self::Other(code) => fmt.write_fmt(format_args!("error code={code}")),
191        }
192    }
193}
194
195
196///Enumeration of valid values for `set_brate`
197#[derive(Copy, Clone)]
198#[repr(u16)]
199pub enum Bitrate {
200    ///8_000
201    Kbps8 = 8,
202    ///16_000
203    Kbps16 = 16,
204    ///24_000
205    Kbps24 = 24,
206    ///32_000
207    Kbps32 = 32,
208    ///40_000
209    Kbps40 = 40,
210    ///48_000
211    Kbps48 = 48,
212    ///64_000
213    Kbps64 = 64,
214    ///80_000
215    Kbps80 = 80,
216    ///96_000
217    Kbps96 = 96,
218    ///112_000
219    Kbps112 = 112,
220    ///128_000
221    Kbps128 = 128,
222    ///160_000
223    Kbps160 = 160,
224    ///192_000
225    Kbps192 = 192,
226    ///224_000
227    Kbps224 = 224,
228    ///256_000
229    Kbps256 = 256,
230    ///320_000
231    Kbps320 = 320,
232}
233
234///Alias to `Bitrate` with incorrect spelling
235pub use Bitrate as Birtate;
236
237#[derive(Copy, Clone)]
238#[repr(u8)]
239///Possible VBR types
240pub enum VbrMode {
241    ///Off.
242    Off = ffi::vbr_mode::vbr_off as u8,
243    ///MT.
244    Mt = ffi::vbr_mode::vbr_mt as u8,
245    ///RH.
246    Rh = ffi::vbr_mode::vbr_rh as u8,
247    ///ABR.
248    Abr = ffi::vbr_mode::vbr_abr as u8,
249    ///MTRH.
250    Mtrh = ffi::vbr_mode::vbr_mtrh as u8,
251}
252
253impl Default for VbrMode {
254    #[inline(always)]
255    fn default() -> Self {
256        Self::Mtrh
257    }
258}
259
260#[derive(Copy, Clone)]
261#[repr(u8)]
262///Possible modes for encoder
263pub enum Mode {
264    ///Mono.
265    Mono = ffi::MPEG_mode::MONO as u8,
266    ///Stereo.
267    Stereo = ffi::MPEG_mode::STEREO as u8,
268    ///Joint stereo.
269    JointStereo = ffi::MPEG_mode::JOINT_STEREO as u8,
270    ///Dual channel
271    ///
272    ///Unsupported so far.
273    DaulChannel = ffi::MPEG_mode::DUAL_CHANNEL as u8,
274    ///Not set.
275    NotSet = ffi::MPEG_mode::NOT_SET as u8,
276}
277
278///Possible quality parameter.
279///From best(0) to worst(9)
280#[derive(Copy, Clone)]
281#[repr(u8)]
282pub enum Quality {
283    ///Best possible quality
284    Best = 0,
285    ///Second best
286    SecondBest = 1,
287    ///Close to best
288    NearBest = 2,
289    ///Very nice
290    VeryNice = 3,
291    ///Nice
292    Nice = 4,
293    ///Good
294    Good = 5,
295    ///Decent
296    Decent = 6,
297    ///Okayish
298    Ok = 7,
299    ///Almost worst
300    SecondWorst = 8,
301    ///Worst
302    Worst = 9,
303}
304
305///ID3 tag as raw bytes.
306///
307///Use empty slice for `None`
308///
309///At the current moment, only up to 250 characters will be copied.
310pub struct Id3Tag<'a> {
311    ///Track's Title
312    pub title: &'a [u8],
313    ///Artist name
314    pub artist: &'a [u8],
315    ///Album name
316    pub album: &'a [u8],
317    ///Album art
318    ///
319    ///Must be image data.
320    ///
321    ///Allowed formats: PNG, JPG, GIF
322    ///
323    ///Maximum size is defined by constant MAX_ALBUM_ART_SIZE
324    ///When setting this metadata, make sure allocate at least MAX_ALBUM_ART_SIZE
325    pub album_art: &'a [u8],
326    ///Year
327    pub year: &'a [u8],
328    ///Comment
329    pub comment: &'a [u8],
330}
331
332impl Id3Tag<'_> {
333    #[inline(always)]
334    ///Returns true if any is set
335    pub const fn is_any_set(&self) -> bool {
336        !self.title.is_empty() || !self.artist.is_empty() || !self.album.is_empty() || !self.album_art.is_empty() || !self.year.is_empty() || !self.comment.is_empty()
337    }
338}
339
340///Builder of C LAME encoder.
341pub struct Builder {
342    inner: NonNull<ffi::lame_global_flags>,
343}
344
345impl Builder {
346    #[inline]
347    ///Creates new encoder with default parameters: J-Stereo, 44.1khz 128kbps CBR mp3 file at quality 5
348    ///
349    ///Returns `None` if unable to allocate struct.
350    pub fn new() -> Option<Self> {
351        let ptr = unsafe {
352            ffi::lame_init()
353        };
354
355        NonNull::new(ptr).map(|inner| Self {
356            inner
357        })
358    }
359
360    #[inline(always)]
361    ///Get access to underlying LAME structure, without dropping ownership.
362    ///
363    ///User must guarantee not to close or dealloc this pointer
364    pub unsafe fn as_ptr(&mut self) -> *mut ffi::lame_global_flags {
365        self.ptr()
366    }
367
368    #[inline(always)]
369    fn ptr(&mut self) -> *mut ffi::lame_global_flags {
370        self.inner.as_ptr()
371    }
372
373    #[inline]
374    ///Sets sample rate.
375    ///
376    ///Defaults to 44_100
377    ///
378    ///Returns whether it is supported or not.
379    pub fn set_sample_rate(&mut self, rate: u32) -> Result<(), BuildError> {
380        let res = unsafe {
381            ffi::lame_set_in_samplerate(self.ptr(), rate.try_into().unwrap_or(libc::c_int::MAX))
382        };
383
384        BuildError::from_c_int(res)
385    }
386
387    #[inline]
388    ///Sets sample rate.
389    ///
390    ///Defaults to 2
391    ///
392    ///Returns whether it is supported or not.
393    pub fn set_num_channels(&mut self, num: u8) -> Result<(), BuildError> {
394        let res = unsafe {
395            ffi::lame_set_num_channels(self.ptr(), num as _)
396        };
397
398        BuildError::from_c_int(res)
399    }
400
401    #[inline]
402    ///Sets bitrate (as kbps).
403    ///
404    ///Defaults to compression ratio of 11.
405    ///
406    ///Returns whether it is supported or not.
407    pub fn set_brate(&mut self, brate: Bitrate) -> Result<(), BuildError> {
408        let res = unsafe {
409            ffi::lame_set_brate(self.ptr(), brate as _)
410        };
411
412        BuildError::from_c_int(res)
413    }
414
415    #[inline]
416    ///Sets MPEG mode.
417    ///
418    ///Default is picked by LAME depending on compression ration and input channels.
419    ///
420    ///Returns whether it is supported or not.
421    pub fn set_mode(&mut self, mode: Mode) -> Result<(), BuildError> {
422        let res = unsafe {
423            ffi::lame_set_mode(self.ptr(), mode as _)
424        };
425
426        BuildError::from_c_int(res)
427    }
428
429    #[inline]
430    ///Sets quality.
431    ///
432    ///Default is good one(5)
433    ///
434    ///Returns whether it is supported or not.
435    pub fn set_quality(&mut self, quality: Quality) -> Result<(), BuildError> {
436        let res = unsafe {
437            ffi::lame_set_quality(self.ptr(), quality as _)
438        };
439
440        BuildError::from_c_int(res)
441    }
442
443    #[inline]
444    ///Sets VBR quality.
445    ///
446    ///Returns whether it is supported or not.
447    pub fn set_vbr_quality(&mut self, quality: Quality) -> Result<(), BuildError> {
448        let res = unsafe {
449            ffi::lame_set_VBR_q(self.ptr(), quality as _)
450        };
451
452        BuildError::from_c_int(res)
453    }
454
455
456    #[inline]
457    ///Sets whether to write VBR tag.
458    ///
459    ///Default is true
460    ///
461    ///Returns whether it is supported or not.
462    pub fn set_to_write_vbr_tag(&mut self, value: bool) -> Result<(), BuildError> {
463        let res = unsafe {
464            ffi::lame_set_bWriteVbrTag(self.ptr(), value as _)
465        };
466
467        BuildError::from_c_int(res)
468    }
469
470    #[inline]
471    ///Sets VBR mode
472    ///
473    ///Default is off (i.e. CBR)
474    ///
475    ///Returns whether it is supported or not.
476    pub fn set_vbr_mode(&mut self, value: VbrMode) -> Result<(), BuildError> {
477        let res = unsafe {
478            ffi::lame_set_VBR(self.ptr(), value as _)
479        };
480
481        BuildError::from_c_int(res)
482    }
483
484    #[inline]
485    ///Sets id3tag tag.
486    ///
487    ///If [FlushGap](FlushGap) is used, then `v1` will not be added.
488    ///But `v2` is always added at the beginning.
489    ///
490    ///Returns whether it is supported or not.
491    pub fn set_id3_tag(&mut self, value: Id3Tag<'_>) -> Result<(), Id3TagError> {
492        if !value.is_any_set() {
493            return Ok(());
494        }
495
496        const MAX_BUFFER: usize = 250;
497        let mut buffer = [0u8; MAX_BUFFER + 1];
498
499        unsafe {
500            ffi::id3tag_init(self.ptr());
501            ffi::id3tag_add_v2(self.ptr());
502
503            if !value.album_art.is_empty() {
504                let size = value.album_art.len();
505                if size > MAX_ALBUM_ART_SIZE {
506                    return Err(Id3TagError::AlbumArtOverflow);
507                }
508                let ptr = value.album_art.as_ptr();
509                ffi::id3tag_set_albumart(self.ptr(), ptr as _, size);
510            }
511
512            if !value.title.is_empty() {
513                let size = cmp::min(MAX_BUFFER, value.title.len());
514                ptr::copy_nonoverlapping(value.title.as_ptr(), buffer.as_mut_ptr(), size);
515                buffer[size] = 0;
516                ffi::id3tag_set_title(self.ptr(), buffer.as_ptr() as _);
517            }
518
519            if !value.album.is_empty() {
520                let size = cmp::min(MAX_BUFFER, value.album.len());
521                ptr::copy_nonoverlapping(value.album.as_ptr(), buffer.as_mut_ptr(), size);
522                buffer[size] = 0;
523                ffi::id3tag_set_album(self.ptr(), buffer.as_ptr() as _);
524            }
525
526            if !value.artist.is_empty() {
527                let size = cmp::min(MAX_BUFFER, value.artist.len());
528                ptr::copy_nonoverlapping(value.artist.as_ptr(), buffer.as_mut_ptr(), size);
529                buffer[size] = 0;
530                ffi::id3tag_set_artist(self.ptr(), buffer.as_ptr() as _);
531            }
532
533            if !value.year.is_empty() {
534                let size = cmp::min(MAX_BUFFER, value.year.len());
535                ptr::copy_nonoverlapping(value.year.as_ptr(), buffer.as_mut_ptr(), size);
536                buffer[size] = 0;
537                ffi::id3tag_set_year(self.ptr(), buffer.as_ptr() as _);
538            }
539
540            if !value.comment.is_empty() {
541                let size = cmp::min(MAX_BUFFER, value.comment.len());
542                ptr::copy_nonoverlapping(value.comment.as_ptr(), buffer.as_mut_ptr(), size);
543                buffer[size] = 0;
544                ffi::id3tag_set_comment(self.ptr(), buffer.as_ptr() as _);
545            }
546        }
547
548        Ok(())
549    }
550
551    #[inline]
552    ///Attempts to initialize encoder with specified parameters.
553    ///
554    ///Returns `None` if parameters are invalid or incompatible.
555    pub fn build(mut self) -> Result<Encoder, BuildError> {
556        let res = unsafe {
557            ffi::lame_init_params(self.ptr())
558        };
559
560        match BuildError::from_c_int(res) {
561            Ok(()) => {
562                let inner = self.inner;
563                mem::forget(self);
564                Ok(Encoder { inner })
565            },
566            Err(error) => Err(error),
567        }
568    }
569}
570
571impl Drop for Builder {
572    #[inline]
573    fn drop(&mut self) {
574        unsafe {
575            ffi::lame_close(self.ptr())
576        };
577    }
578}
579
580///LAME Encoder.
581pub struct Encoder {
582    inner: NonNull<ffi::lame_global_flags>,
583}
584
585impl Encoder {
586    #[inline(always)]
587    fn ptr(&self) -> *mut ffi::lame_global_flags {
588        self.inner.as_ptr()
589    }
590
591    #[inline]
592    ///Returns sample rate.
593    pub fn sample_rate(&self) -> u32 {
594        unsafe {
595            ffi::lame_get_in_samplerate(self.ptr()) as u32
596        }
597    }
598
599    #[inline]
600    ///Returns number of channels.
601    pub fn num_channels(&self) -> u8 {
602        unsafe {
603            ffi::lame_get_num_channels(self.ptr()) as u8
604        }
605    }
606
607    #[inline]
608    ///Attempts to encode PCM data, writing whatever available onto `output` buffer
609    ///
610    ///### Arguments:
611    ///
612    /// - `input` - Data input. Can be [MonoPcm](MonoPcm), [DualPcm](DualPcm) or [InterleavedPcm](InterleavedPcm)
613    /// - `output` - Output buffer to write into.
614    ///
615    ///### Result:
616    ///On success, returns number of bytes written (can be 0).
617    ///Otherwise returns error indicating potential issue.
618    pub fn encode(&mut self, input: impl EncoderInput, output: &mut [MaybeUninit<u8>]) -> Result<usize, EncodeError> {
619        let output_len = output.len();
620        let output_buf = output.as_mut_ptr();
621
622        let result = input.encode(self, output_buf as _, output_len);
623
624        EncodeError::from_c_int(result)
625    }
626
627    #[inline(always)]
628    ///Attempts to encode PCM data, writing whatever available onto `output` buffer
629    ///
630    ///`output` size is adjusted on success only
631    ///
632    ///Refer for details to `encode()`
633    pub fn encode_to_vec(&mut self, input: impl EncoderInput, output: &mut Vec<u8>) -> Result<usize, EncodeError> {
634        let original_len = output.len();
635        match self.encode(input, output.spare_capacity_mut()) {
636            Ok(written) => {
637                unsafe {
638                    output.set_len(original_len.saturating_add(written));
639                }
640                Ok(written)
641            },
642            Err(error) => Err(error),
643        }
644    }
645
646    #[inline]
647    ///Attempts flush all data, writing whatever available onto `output` buffer
648    ///Padding with 0 to complete MP3
649    ///
650    ///### Type:
651    ///
652    ///- [FlushNoGap](FlushNoGap) - performs flush, using ancillary data to fill gaps;
653    ///- [FlushGap](FlushGap) - performs flush, padding with 0;
654    ///
655    ///### Arguments:
656    ///
657    /// - `output` - Output buffer to write into. As it is final action, you need at least 7200 bytes to hold at MP3 data.
658    ///
659    ///### Result:
660    ///On success, returns number of bytes written (can be 0).
661    ///Otherwise returns error indicating potential issue.
662    pub fn flush<T: EncoderFlush>(&mut self, output: &mut [MaybeUninit<u8>]) -> Result<usize, EncodeError> {
663        let output_len = output.len();
664        let output_buf = output.as_mut_ptr();
665
666        let result = T::flush(self, output_buf as _, output_len);
667
668        EncodeError::from_c_int(result)
669    }
670
671    #[inline(always)]
672    ///Attempts flush all data, writing whatever available onto `output` buffer.
673    ///
674    ///`output` size is adjusted on success only
675    ///
676    ///Refer for details to `flush()`
677    pub fn flush_to_vec<T: EncoderFlush>(&mut self, output: &mut Vec<u8>) -> Result<usize, EncodeError> {
678        let original_len = output.len();
679        match self.flush::<T>(output.spare_capacity_mut()) {
680            Ok(written) => {
681                unsafe {
682                    output.set_len(original_len.saturating_add(written));
683                }
684                Ok(written)
685            },
686            Err(error) => Err(error),
687        }
688    }
689}
690
691impl Drop for Encoder {
692    #[inline]
693    fn drop(&mut self) {
694        unsafe {
695            ffi::lame_close(self.ptr())
696        };
697    }
698}
699
700/// According to LAME 3.99.5 HACKING, it is thread-safe.
701unsafe impl Send for Encoder {}
702/// According to LAME 3.99.5 HACKING, it is thread-safe.
703unsafe impl Sync for Encoder {}
704
705///Creates default encoder with 192kbps bitrate and best possible quality.
706pub fn encoder() -> Result<Encoder, BuildError> {
707    match Builder::new() {
708        Some(mut builder) => {
709            builder.set_brate(Bitrate::Kbps192)?;
710            builder.set_quality(Quality::Best)?;
711            builder.build()
712        },
713        None => Err(BuildError::NoMem)
714    }
715}