Files
gemigreerd-racket-audio/ffmpeg-definitions.rkt
T
2026-05-11 09:40:11 +02:00

1265 lines
39 KiB
Racket

#lang racket/base
(require ffi/unsafe
ffi/unsafe/define
ffi/unsafe/alloc
(for-syntax racket/base)
"private/utils.rkt"
"private/cstruct-helper.rkt"
let-assert
define-return
)
(provide fmpg-init
fmpg-open-file!
fmpg-close!
fmpg-is-open
fmpg-audio-stream-count
fmpg-audio-sample-rate
fmpg-audio-channels
fmpg-audio-bits-per-sample
fmpg-audio-bytes-per-sample
fmpg-duration-ms
fmpg-duration-samples
fmpg-file-bitrate
fmpg-decode-next!
fmpg-seek-ms!
fmpg-buffer
fmpg-buffer-size
fmpg-buffer-start-sample
fmpg-buffer-end-sample
fmpg-sample-position
ffmpeg-version)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; C - Types & Functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Helpers for constants
(define (mktag a b c d)
(bitwise-ior (char->integer a)
(arithmetic-shift (char->integer b) 8)
(arithmetic-shift (char->integer c) 16)
(arithmetic-shift (char->integer d) 24)))
(define (fferrtag a b c d)
(- (mktag a b c d)))
(define (AVERROR e) (* -1 e))
;;;; Constants
(define-cstruct _AVRational
([num _int]
[den _int]
)
)
(define EAGAIN 11) ; from errno.h
(define INT_MAX (sub1 (arithmetic-shift 1 31)))
(define AV_NOPTS_VALUE #x8000000000000000)
(define AV_TIME_BASE 1000000)
(define AV_TIME_BASE_Q (make-AVRational 1 AV_TIME_BASE))
(define AVSEEK_FLAG_BACKWARD 1)
(define AVERROR_EAGAIN (AVERROR EAGAIN))
(define AVERROR_EOF (fferrtag #\E #\O #\F #\space))
(define _AVCodec (_cpointer/null 'AVCodec))
(define _AVCodecContext (_cpointer/null 'AVCodecContext))
(define _SwrContext (_cpointer/null 'SwrContext))
;;;; enum constants
(define AV_SAMPLE_FMT_NONE -1)
(define AV_SAMPLE_FMT_S32 2)
(define AVMEDIA_TYPE_AUDIO 1)
;;;; potential enum types
(define _AVCodecID _int)
(define _AVMediaType _int)
(define _AVSampleFormat _int)
;;;; struct types and partial struct types.
;;;; the least necessary for ffmpeg and wrappers
(define-cstruct _AVChannelLayout
([order _int] ; enum AVChannelOrder
[nb_channels _int]
[u (_union _uint64 _pointer)]
[opaque _pointer]
)
)
; _AVCodecParameters:
; codec_type : AVMediaType
; codec_id : int / AVCodecID
; ch_layout : AVChannelLayout
; sample_rate : int
(def-cstruct
_AVCodecParameters
(codec_type codec_id format ch_layout sample_rate)
(make-offsets (codec_type _AVMediaType)
(codec_id _AVCodecID)
;; codec_tag, extradata, extradata_size,
;; coded_side_data, nb_coded_side_data
_int32 _pointer _int _pointer _int
;; AVCodecParameters.format
;; audio: enum AVSampleFormat
(format _AVSampleFormat)
_int64 ; bit_rate
(6 _int)
(2 _AVRational)
(7 _int)
(ch_layout _AVChannelLayout)
(sample_rate _int))
)
(define (avcodec-pars-codec_id s)
(AVCodecParameters-codec_id s))
(define (avcodec-pars-codec_type s)
(AVCodecParameters-codec_type s))
(define (avcodec-pars-sample_rate s)
(AVCodecParameters-sample_rate s))
(define (avcodec-pars-channels s)
(AVChannelLayout-nb_channels
(AVCodecParameters-ch_layout s)))
(define (avcodec-pars-format s)
(AVCodecParameters-format s))
; _AVStream:
; codecpar : AVCodecParameters*
; time_base : AVRational
; duration : int64
(def-cstruct
_AVStream
(codec time_base duration)
(make-offsets _pointer (2 _int) (codec _AVCodecParameters-pointer) _pointer (time_base _AVRational) _int64 (duration _int64))
)
(define (avstream-codec s)
(AVStream-codec s))
(define (avstream-duration s)
(AVStream-duration s))
(define (avstream-time_base s)
(AVStream-time_base s))
; _AVFormatContext:
; nb_streams : uint/int
; streams : AVStream**
; duration : int64
; bit_rate : int64
(def-cstruct
_AVFormatContext
(nb_streams streams duration bit_rate)
(make-offsets (5 _pointer) _int (nb_streams _int) (streams _pointer) _uint _pointer _uint _pointer _string*/utf-8 _int64 (duration _int64) (bit_rate _int64))
)
(define (avformat_nb_streams s)
(AVFormatContext-nb_streams s))
(define (avformat_stream s i)
(let ((streams-ptr (AVFormatContext-streams s)))
(ptr-ref streams-ptr _AVStream-pointer i)
))
(define (avformat_duration s)
(AVFormatContext-duration s))
(define (avformat_bit_rate s)
(AVFormatContext-bit_rate s))
; AVFrame
; data
; nb_samples
; best_effort_timestamp
(def-cstruct
_AVFrame
(data nb_samples sample_rate best_effort_timestamp)
(make-offsets
(data _pointer)
(7 _pointer) ; data
(8 _int) ; linesize
_pointer ; extended-data
(2 _int) ; width, height
(nb_samples _int) ; nb_samples
(2 _int) ; format / enum Picturetype
_AVRational ; sample_aspect_ratio;
(2 _int64) ; pts / pkt_dts
_AVRational ; time_base
_int ; quality
_pointer ; opaque
_int ; repeat_pict
(sample_rate _int); sample_rate
(8 _pointer) ; AVBufferRef *buf[AV_NUM_DATA_POINTERS];
_pointer ; AVBufferRef **extended_buf;
_int ; int nb_extended_buf;
_pointer ; AVFrameSideData **side_data;
_int ; nb_side_data;
(6 _int) ; flags / color_range / color_primaries / color_trc / colorspace / chroma_location
(best_effort_timestamp _int64) ; best_effort_timestamp
))
(define (avframe-data frame)
;; swr_convert wil const uint8_t **.
;; AVFrame begint met uint8_t *data[8], dus de frame pointer zelf
;; is het adres van data[0].
frame)
(define (avframe-data0 frame)
;; Alleen als je ooit de eerste plane pointer zelf nodig hebt.
(AVFrame-data frame))
(define (avframe-nb-samples frame)
(AVFrame-nb_samples frame))
(define (avframe-best-effort-timestamp frame)
(AVFrame-best_effort_timestamp frame))
; AVPacket
; stream_index
(def-cstruct
_AVPacket
(stream_index)
(make-offsets
_pointer ; buf
_int64 ; pts
_int64 ; dts
_pointer ; data
_int ; size
(stream_index _int)))
(define (avpacket-stream-index pkt)
(AVPacket-stream_index pkt))
;;;; Needed functions for audio decoder
(define libavutil (get-lib (case (system-type 'os)
[(windows) '("avutil-60")]
[else '("avutil" "libavutil")]) '(#f)))
(define libswresample (get-lib (case (system-type 'os)
[(windows) '("swresample-6")]
[else '("swresample" "libswresample")]) '(#f)))
(define libavcodec (get-lib (case (system-type 'os)
[(windows) '("avcodec-62")]
[else '("avcodec" "libavcodec")]) '(#f)))
(define libavformat (get-lib (case (system-type 'os)
[(windows) '("avformat-62")]
[else '("avformat" "libavformat")]) '(#f)))
(define-ffi-definer def-avutil libavutil #:default-make-fail make-not-available)
(define-ffi-definer def-swresample libswresample #:default-make-fail make-not-available)
(define-ffi-definer def-avcodec libavcodec #:default-make-fail make-not-available)
(define-ffi-definer def-avformat libavformat #:default-make-fail make-not-available)
(def-avutil avutil_version (_fun -> _uint))
(def-avcodec avcodec_version (_fun -> _uint))
(def-avformat avformat_version (_fun -> _uint))
(def-swresample swresample_version (_fun -> _uint))
(def-avcodec avcodec_free_context/raw (_fun (_ptr io _AVCodecContext)
-> (p : _AVCodecContext)
-> p ) #:c-id avcodec_free_context)
(define (avcodec_free_context ctx)
(if ctx
(begin (avcodec_free_context/raw ctx) #f)
#f))
(def-swresample swr_free/raw (_fun (_ptr io _SwrContext)
-> (p : _SwrContext)
-> p) #:c-id swr_free)
(define (swr_free ctx)
(if ctx
(begin (swr_free/raw ctx) #f)
#f))
(def-avformat avformat_close_input/raw (_fun (_ptr io _AVFormatContext-pointer/null)
-> (p : _AVFormatContext-pointer/null)
-> p) #:c-id avformat_close_input)
(define (avformat_close_input inp)
(if inp
(begin (avformat_close_input/raw inp) #f)
#f))
(def-avformat av_find_best_stream
(_fun _AVFormatContext-pointer
_AVMediaType
_int
_int
_pointer ; AVCodec **decoder_ret, hier gewoon #f meegeven
_int
-> _int))
(def-avcodec avcodec_find_decoder
(_fun _AVCodecID -> _AVCodec))
(def-avcodec avcodec_alloc_context3
(_fun _AVCodec -> _AVCodecContext))
(def-avcodec avcodec_parameters_to_context
(_fun _AVCodecContext
_AVCodecParameters-pointer
-> _int))
(def-avcodec avcodec_open2
(_fun _AVCodecContext
_AVCodec
_pointer ; AVDictionary **options, hier nullptr/#f
-> _int))
(def-avutil av_frame_alloc
(_fun -> _AVFrame-pointer/null))
(def-avutil av_frame_unref
(_fun _AVFrame-pointer -> _void))
(def-avutil av_frame_free/raw
(_fun (_ptr io _AVFrame-pointer/null)
-> (p : _AVFrame-pointer/null)
-> p) #:c-id av_frame_free)
(define (av_frame_free frm)
(if frm
(begin (av_frame_free/raw frm) #f)
#f))
(def-swresample swr_alloc_set_opts2
(_fun (ps : (_ptr io _SwrContext))
_AVChannelLayout-pointer ; out_ch_layout
_AVSampleFormat ; out_sample_fmt
_int ; out_sample_rate
_AVChannelLayout-pointer ; in_ch_layout
_AVSampleFormat ; in_sample_fmt
_int ; in_sample_rate
_int ; log_offset
_pointer ; log_ctx
-> (r : _int)
-> (values r ps)))
(def-swresample swr_init
(_fun _SwrContext -> _int))
(def-avcodec avcodec_parameters_alloc
(_fun -> _AVCodecParameters-pointer/null))
(def-avcodec avcodec_parameters_from_context
(_fun _AVCodecParameters-pointer
_AVCodecContext
-> _int))
(def-avcodec avcodec_parameters_free/raw
(_fun (_ptr io _AVCodecParameters-pointer/null)
-> (p : _AVCodecParameters-pointer/null)
-> p) #:c-id avcodec_parameters_free)
(define (avcodec_parameters_free par)
(if par
(begin (avcodec_parameters_free/raw par) #f)
#f))
(def-avformat avformat_open_input
(_fun (ctx : (_ptr io _AVFormatContext-pointer/null))
_string/utf-8 ; filename
_pointer ; AVInputFormat *fmt, hier #f
_pointer ; AVDictionary **options, hier #f
-> (r : _int)
-> (values r ctx)))
(def-avformat avformat_find_stream_info
(_fun _AVFormatContext-pointer
_pointer ; AVDictionary **options, hier #f
-> _int))
(def-avutil av_frame_get_best_effort_timestamp
(_fun _AVFrame-pointer -> _int64))
(def-swresample swr_get_out_samples
(_fun _SwrContext _int -> _int))
(def-swresample swr_convert
(_fun _SwrContext _pointer _int _pointer _int -> _int))
(def-swresample swr_get_delay
(_fun _SwrContext _int64 -> _int64))
(def-avutil av_samples_get_buffer_size
(_fun _pointer _int _int _AVSampleFormat _int -> _int))
(def-avcodec avcodec_receive_frame
(_fun _AVCodecContext _AVFrame-pointer -> _int))
(def-avformat av_read_frame
(_fun _AVFormatContext-pointer _AVPacket-pointer -> _int))
(def-avcodec av_packet_alloc
(_fun -> _AVPacket-pointer/null))
(def-avcodec av_packet_unref
(_fun _AVPacket-pointer -> _void))
(def-avcodec av_packet_free/raw
(_fun (_ptr io _AVPacket-pointer/null)
-> (p : _AVPacket-pointer/null)
-> p) #:c-id av_packet_free)
(define (av_packet_free pkg)
(if pkg
(begin (av_packet_free/raw pkg) #f)
#f))
(def-avcodec avcodec_send_packet
(_fun _AVCodecContext _AVPacket-pointer/null -> _int))
(def-avutil av_rescale
(_fun _int64 _int64 _int64 -> _int64))
(def-avutil av_rescale_q
(_fun _int64 _AVRational _AVRational -> _int64))
(def-avformat av_seek_frame
(_fun _AVFormatContext-pointer _int _int64 _int -> _int))
(def-avcodec avcodec_flush_buffers
(_fun _AVCodecContext -> _void))
(def-swresample swr_close
(_fun _SwrContext -> _void))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Version check
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define avutil-version-major (quotient (avutil_version) 65536))
(define avcodec-version-major (quotient (avcodec_version) 65536))
(define avformat-version-major (quotient (avformat_version) 65536))
(define swresample-version-major (quotient (swresample_version) 65536))
(define (ffmpeg-version lib)
(let ((v (λ (v) (list (quotient v 65536) (remainder (quotient v 256) 256) (remainder v 256)))))
(cond ((eq? lib 'avutil) (v (avutil_version)))
((eq? lib 'avcodec) (v (avcodec_version)))
((eq? lib 'avformat) (v (avformat_version)))
((or (eq? lib 'swr)
(eq? lib 'swresample)) (v (swresample_version)))
(else (error (format "Unknown library '~a" lib)))
)
)
)
(define (ffmpeg-version-string lib)
(apply format (cons "~a.~a.~a" (ffmpeg-version lib))))
;; Support ffmpeg 6, 7 and 8
(define-syntax check-support
(syntax-rules ()
((_ lib version-hash)
(let ((from (car (hash-ref version-hash lib)))
(until (cadr (hash-ref version-hash lib))))
(let ((major-version (car (ffmpeg-version lib))))
(cond
((or (< major-version from) (> major-version until))
(error
(format "Unsupported major version of ffmpeg library ~a: ~a (~a).\nSupported range: ~a - ~a"
'lib major-version (ffmpeg-version-string lib) from until)))
(else
(info-sound "Supported ffmpeg library ~a - version ~a between ~a and ~a"
lib (ffmpeg-version-string lib) from until)
)
)
)
)
)
)
)
(check-support 'avutil valid-ffmpeg-versions)
(check-support 'avcodec valid-ffmpeg-versions)
(check-support 'avformat valid-ffmpeg-versions)
(check-support 'swresample valid-ffmpeg-versions)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Constants
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define FMPG_OUTPUT_BITS 32)
(define FMPG_OUTPUT_BYTES 4)
(define FMPG_OUTPUT_FMT AV_SAMPLE_FMT_S32)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Internal structures for ffmpeg decoding
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-syntax def-struct-helpers
(syntax-rules ()
((_ (struct-get get struct-set set))
(begin
(define get struct-get)
(define set struct-set))
)
((_ (struct-get get))
(define get struct-get)
)
)
)
(define-syntax struct-helpers
(syntax-rules ()
((_ a ...)
(begin
(def-struct-helpers a)
...))))
;;;;;;;;;; audio-info-storage
(define-struct audio-info-storage
(audio-stream-count
selected-stream-index
sample-rate
channels
duration-ms
duration-samples
)
#:mutable
#:transparent
)
(struct-helpers
(audio-info-storage-audio-stream-count ais-stream-count
set-audio-info-storage-audio-stream-count! ais-stream-count!)
(audio-info-storage-selected-stream-index ais-stream-index
set-audio-info-storage-selected-stream-index! ais-stream-index!)
(audio-info-storage-sample-rate ais-rate
set-audio-info-storage-sample-rate! ais-rate!)
(audio-info-storage-channels ais-channels
set-audio-info-storage-channels! ais-channels!)
(audio-info-storage-duration-ms ais-duration-ms
set-audio-info-storage-duration-ms! ais-duration-ms!)
(audio-info-storage-duration-samples ais-duration-samples
set-audio-info-storage-duration-samples! ais-duration-samples!)
)
(define (ais-clear! s)
(ais-stream-count! s 0)
(ais-stream-index! s -1)
(ais-rate! s 0)
(ais-channels! s 0)
(ais-duration-ms! s -1)
(ais-duration-samples! s -1)
)
(define (new-audio-info-storage)
(let ((a (make-audio-info-storage 0 -1 0 0 -1 -1)))
a))
;;;;;;;;;;; decoder storage
(define-struct decoder-storage
(codec ; _AVCodec - Not owned by decoder-storage, global pointer
codec-ctx ; _AVCodecContext
frame ; _AVFrame
swr-ctx ; _SwrContext
pcm ; bytes (maybe bytes-buffer)
eof-seen ; boolean
decoder-drained ; boolean
timecode ; double
last-samples ; integer
buffer-start-sample ; integer
next-sample-position ; integer
discard-until-sample ; integer
)
#:mutable
#:transparent
)
(define (pcm-empty? dec)
(zero? (bytes-length (ds-pcm dec))))
(define (pcm-present? dec)
(positive? (bytes-length (ds-pcm dec))))
(define (produced-pcm? produced dec)
(and (> produced 0) (pcm-present? dec)))
(struct-helpers
(decoder-storage-codec ds-codec set-decoder-storage-codec! ds-codec!)
(decoder-storage-codec-ctx ds-codec-ctx set-decoder-storage-codec-ctx! ds-codec-ctx!)
(decoder-storage-frame ds-frame set-decoder-storage-frame! ds-frame!)
(decoder-storage-swr-ctx ds-swr-ctx set-decoder-storage-swr-ctx! ds-swr-ctx!)
(decoder-storage-pcm ds-pcm set-decoder-storage-pcm! ds-pcm!)
(decoder-storage-eof-seen ds-eof-seen set-decoder-storage-eof-seen! ds-eof-seen!)
(decoder-storage-decoder-drained ds-drained set-decoder-storage-decoder-drained! ds-drained!)
(decoder-storage-timecode ds-timecode set-decoder-storage-timecode! ds-timecode!)
(decoder-storage-last-samples ds-last-samples set-decoder-storage-last-samples! ds-last-samples!)
(decoder-storage-buffer-start-sample ds-start-sample
set-decoder-storage-buffer-start-sample! ds-start-sample!)
(decoder-storage-next-sample-position ds-next-sample-pos
set-decoder-storage-next-sample-position! ds-next-sample-pos!)
(decoder-storage-discard-until-sample ds-discard-until
set-decoder-storage-discard-until-sample! ds-discard-until!)
)
(define (free-ffmpeg s)
(when s
(ds-codec-ctx! s (avcodec_free_context (ds-codec-ctx s)))
(ds-frame! s (av_frame_free (ds-frame s)))
(ds-swr-ctx! s (swr_free (ds-swr-ctx s)))
(reset-decoder-storage! s)
)
)
(define (ds-clear-output! s)
(ds-pcm! s (make-bytes 0))
(ds-last-samples! s 0)
(ds-start-sample! s (ds-next-sample-pos s))
)
(define (reset-decoder-storage! s)
(ds-codec! s #f)
(ds-pcm! s (make-bytes 0))
(ds-eof-seen! s #f)
(ds-drained! s #f)
(ds-timecode! s 0.0)
(ds-last-samples! s 0)
(ds-start-sample! s 0)
(ds-next-sample-pos! s 0)
(ds-discard-until! s -1))
(define (new-decoder-storage)
(let ((ds (make-decoder-storage #f #f #f #f
(make-bytes 0)
#f #f
0.0
0 0 0 -1)))
(register-finalizer ds
(λ (s) (free-ffmpeg s)))
ds))
;;;;;;;;;;;;;; fmpg-instance
(define-struct fmpg-instance
(opened ; boolean
format-ctx ; AVFormatContext
audio-info ; audio-info-storage
decoder ; decoder-storage
)
#:mutable
#:transparent
)
(define (new-fmpg-instance)
(let ((i (make-fmpg-instance #f #f (new-audio-info-storage) (new-decoder-storage))))
(register-finalizer i
(λ (i)
(let ((ctx (fmpg-instance-format-ctx i)))
(when ctx
(set-fmpg-instance-format-ctx! i (avformat_close_input ctx))))
(set-fmpg-instance-audio-info! i #f)
(set-fmpg-instance-decoder! i #f))
)
i))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Helper functions for ffmpeg decoding
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-syntax av_q2d
(syntax-rules ()
((_ a)
(exact->inexact (/ (AVRational-num a) (AVRational-den a)))
)
)
)
(define (count-audio-streams avformat-ctx)
(if (eq? avformat-ctx #f)
0
(let ((count 0)
(nb_streams (avformat_nb_streams avformat-ctx))
(i 0)
)
(while (< i nb_streams)
(let* ((stream (avformat_stream avformat-ctx i))
(par (avstream-codec stream))
)
(unless (eq? par #f)
(let ((codec-type (avcodec-pars-codec_type par)))
(when (= codec-type AVMEDIA_TYPE_AUDIO)
(set! count (+ count 1))
)
)
)
)
(set! i (+ i 1))
)
count)
)
)
(define (milliseconds_from_seconds seconds)
(if (< seconds 0.0)
-1
(inexact->exact (round (+ (* seconds 1000.0) 0.5)))))
(define (samples_from_seconds seconds sample-rate)
(if (or (< seconds 0.0) (<= sample-rate 0))
-1
(inexact->exact (round (+ (* seconds sample-rate) 0.5)))))
(define (stream_duration_seconds stream)
(if (eq? stream #f)
-1.0
(if (= (avstream-duration stream) AV_NOPTS_VALUE)
-1.0
(* (avstream-duration stream) (av_q2d (avstream-time_base stream)))
)
)
)
(define (format_duration_seconds ctx)
(if (eq? ctx #f)
-1
(if (= (avformat_duration ctx) AV_NOPTS_VALUE)
-1.0
(exact->inexact (/ (avformat_duration ctx) AV_TIME_BASE))
)
)
)
(define (timestamp_to_samples timestamp stream sample_rate)
(if (or (eq? stream #f) (= timestamp AV_NOPTS_VALUE) (<= sample_rate 0))
-1
(let ((seconds (exact->inexact
(* timestamp (av_q2d (avstream-time_base stream))))))
(samples_from_seconds seconds sample_rate))))
(define (filename->string filename)
(cond
[(path? filename) (path->string filename)]
[(string? filename) filename]
[else #f]))
(define (fill-audio-info! self)
(let* ((ctx (fmpg-instance-format-ctx self))
(info (fmpg-instance-audio-info self)))
(ais-clear! info)
(ais-stream-count! info (count-audio-streams ctx))
(let/assert
((best (av_find_best_stream ctx AVMEDIA_TYPE_AUDIO -1 -1 #f 0) (a->=? 0) #f)
(stream (avformat_stream ctx best) a-!nullptr? #f)
(par (avstream-codec stream) a-!nullptr? #f)
(codec-type (avcodec-pars-codec_type par) (a-=? AVMEDIA_TYPE_AUDIO) #f)
(sample-rate (avcodec-pars-sample_rate par) (a->? 0) #f)
(channels (avcodec-pars-channels par) (a->? 0) #f)
(stream-seconds (stream_duration_seconds stream))
(seconds (if (< stream-seconds 0.0)
(format_duration_seconds ctx)
stream-seconds))
)
(begin
(ais-stream-index! info best)
(ais-rate! info sample-rate)
(ais-channels! info channels)
(ais-duration-ms! info (milliseconds_from_seconds seconds))
(ais-duration-samples! info (samples_from_seconds seconds sample-rate))
#t
)
)
)
)
(define (instance-ready? instance)
(and instance
(fmpg-instance-opened instance)
(fmpg-instance-format-ctx instance)
(let ((info (fmpg-instance-audio-info instance)))
(and info
(>= (ais-stream-index info) 0)))
(let ((dec (fmpg-instance-decoder instance)))
(and dec
(ds-codec-ctx dec)
(ds-swr-ctx dec)))
#t))
(define (init-codec-context! self)
(let/assert
((dec (fmpg-instance-decoder self))
(info (fmpg-instance-audio-info self))
(ctx (fmpg-instance-format-ctx self))
(stream-index (ais-stream-index info))
(stream (avformat_stream ctx stream-index) a-!nullptr? #f)
(par (avstream-codec stream) a-!nullptr? #f)
(codec (let ((c (avcodec_find_decoder (avcodec-pars-codec_id par))))
(ds-codec! dec c)
c)
a-!nullptr? #f)
(codec-ctx (let ((c (avcodec_alloc_context3 codec)))
(ds-codec-ctx! dec c)
c)
a-!nullptr? #f)
(ret-par (avcodec_parameters_to_context codec-ctx par) (a->=? 0) #f)
(ret-open (avcodec_open2 codec-ctx codec #f) (a->=? 0) #f)
(frame (let ((f (av_frame_alloc)))
(ds-frame! dec f)
f)
a-!nullptr? #f))
#t))
(define (init-resampler! self)
(let/assert
((dec (fmpg-instance-decoder self))
(codec-ctx (ds-codec-ctx dec) a-!nullptr? #f)
(par (avcodec_parameters_alloc) a-!nullptr? #f))
(let ((result
(let/assert
((ret-par (avcodec_parameters_from_context par codec-ctx) (a->=? 0) #f)
(layout (AVCodecParameters-ch_layout par))
(channels (AVChannelLayout-nb_channels layout) (a->? 0) #f)
(rate (avcodec-pars-sample_rate par) (a->? 0) #f)
(fmt (avcodec-pars-format par))
(ret-swr (let-values (((ret swr-ctx)
(swr_alloc_set_opts2 (ds-swr-ctx dec)
layout FMPG_OUTPUT_FMT rate
layout fmt rate
0 #f)))
(ds-swr-ctx! dec swr-ctx)
ret)
(a->=? 0) #f)
(ret-init (swr_init (ds-swr-ctx dec)) (a->=? 0) #f))
#t)))
(avcodec_parameters_free par)
result)))
(define (init-decoder! self)
(let ((dec (fmpg-instance-decoder self)))
(free-ffmpeg dec)
(reset-decoder-storage! dec)
(and (init-codec-context! self)
(init-resampler! self))))
(define (fmpg-init)
(with-handlers ([exn:fail? (lambda (e) #f)])
(new-fmpg-instance)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; API
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (fmpg-open-file! instance filename)
(let/assert
((instance instance a-!nullptr? 0)
(opened (fmpg-instance-opened instance) a-false? 0)
(old-ctx (fmpg-instance-format-ctx instance) a-nullptr? 0)
(filename (filename->string filename) string? 0))
(let ((result
(let/assert
((ret-open (let-values (((r ctx) (avformat_open_input #f filename #f #f)))
(set-fmpg-instance-format-ctx! instance ctx)
r)
(a->=? 0) 0)
(ctx (fmpg-instance-format-ctx instance) a-!nullptr? 0)
(ret-info (avformat_find_stream_info ctx #f) (a->=? 0) 0)
(info-ok (fill-audio-info! instance) a-true? 0)
(dec-ok (init-decoder! instance) a-true? 0))
(begin
(set-fmpg-instance-opened! instance #t)
1))))
(when (zero? result) (fmpg-close! instance))
result)))
(define (fmpg-close! instance)
(when instance
(free-ffmpeg (fmpg-instance-decoder instance))
(set-fmpg-instance-format-ctx! instance
(avformat_close_input
(fmpg-instance-format-ctx instance)))
(set-fmpg-instance-opened! instance #f)
(let ((info (fmpg-instance-audio-info instance)))
(when info (ais-clear! info)))))
(define (fmpg-is-open instance)
(if (instance-ready? instance) 1 0))
(define (fmpg-audio-stream-count instance)
(if (and instance
(fmpg-instance-opened instance))
(ais-stream-count (fmpg-instance-audio-info instance))
0))
(define (fmpg-audio-sample-rate instance)
(if (instance-ready? instance)
(ais-rate (fmpg-instance-audio-info instance))
0))
(define (fmpg-audio-channels instance)
(if (instance-ready? instance)
(ais-channels (fmpg-instance-audio-info instance))
0))
(define (fmpg-audio-bits-per-sample instance)
FMPG_OUTPUT_BITS)
(define (fmpg-audio-bytes-per-sample instance)
FMPG_OUTPUT_BYTES)
(define (fmpg-duration-ms instance)
(if (instance-ready? instance)
(ais-duration-ms (fmpg-instance-audio-info instance))
-1))
(define (fmpg-duration-samples instance)
(if (instance-ready? instance)
(ais-duration-samples (fmpg-instance-audio-info instance))
-1))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Decoding
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (append-bytes! dec src nbytes)
(cond
[(zero? nbytes) #t]
[else
(let/assert
((src src a-!nullptr? #f)
(pcm (ds-pcm dec))
(old-size (bytes-length pcm))
(new-size (+ old-size nbytes) (a-<=? INT_MAX) #f))
(let ((new-pcm (make-bytes new-size)))
(bytes-copy! new-pcm 0 pcm 0 old-size)
(memcpy new-pcm old-size src 0 nbytes)
(ds-pcm! dec new-pcm)
#t))]))
(define (select-frame-range! dec frame-start out-samples channels)
(let ((keep-start frame-start)
(keep-samples out-samples)
(byte-offset 0)
(dropped-all? #f))
(when (>= (ds-discard-until dec) 0)
(let* ((target (ds-discard-until dec))
(frame-end (+ frame-start out-samples)))
(cond
[(<= frame-end target)
(ds-next-sample-pos! dec frame-end)
(set! keep-samples 0)
(set! dropped-all? #t)]
[else
(when (< frame-start target)
(let ((drop (- target frame-start)))
(when (and (> drop 0) (< drop out-samples))
(set! byte-offset (* drop channels FMPG_OUTPUT_BYTES))
(set! keep-samples (- out-samples drop))
(set! keep-start target))))
(ds-discard-until! dec -1)])))
(values keep-start keep-samples byte-offset dropped-all?)))
(define (call-with-swr-output-buffer max-bytes proc)
(let/assert
((tmp (malloc max-bytes 'raw) a-!nullptr? #f)
(out-planes (malloc _pointer 1 'raw) a-!nullptr? #f))
(ptr-set! out-planes _pointer 0 tmp)
(let ((result (proc tmp out-planes)))
(free out-planes)
(free tmp)
result)))
(define (append-converted-frame! self frame)
(let/assert
((dec (fmpg-instance-decoder self))
(info (fmpg-instance-audio-info self))
(channels (ais-channels info))
(sample-rate (ais-rate info))
(nb-samples (avframe-nb-samples frame)))
(cond
[(or (<= channels 0) (<= nb-samples 0)) #t]
[else
(let/assert
((max-out-samples (swr_get_out_samples (ds-swr-ctx dec) nb-samples) (a->? 0) #f)
(max-bytes (av_samples_get_buffer_size #f channels max-out-samples FMPG_OUTPUT_FMT 1) (a->? 0) #f))
(call-with-swr-output-buffer
max-bytes
(lambda (tmp out-planes)
(let/assert
((out-samples (swr_convert (ds-swr-ctx dec) out-planes max-out-samples
(avframe-data frame) nb-samples)
(a->=? 0) #f)
(used-bytes (av_samples_get_buffer_size #f channels out-samples FMPG_OUTPUT_FMT 1) (a->=? 0) #f)
(stream-index (ais-stream-index info))
(stream (avformat_stream (fmpg-instance-format-ctx self) stream-index) a-!nullptr? #f)
(frame-start0 (timestamp_to_samples (avframe-best-effort-timestamp frame) stream sample-rate))
(frame-start (if (< frame-start0 0) (ds-next-sample-pos dec) frame-start0)))
(let-values (((keep-start keep-samples byte-offset dropped-all?)
(select-frame-range! dec frame-start out-samples channels)))
(cond
[dropped-all? #t]
[(<= keep-samples 0)
(ds-next-sample-pos! dec (+ frame-start out-samples))
#t]
[else
(when (pcm-empty? dec)
(ds-start-sample! dec keep-start)
(ds-timecode! dec (/ (exact->inexact keep-start) (exact->inexact sample-rate))))
(let ((keep-bytes (* keep-samples channels FMPG_OUTPUT_BYTES)))
(let/assert
((ok? (append-bytes! dec (ptr-add tmp byte-offset) keep-bytes) (a-!eq? #f) #f))
(ds-last-samples! dec (+ (ds-last-samples dec) keep-samples))
(ds-next-sample-pos! dec (+ keep-start keep-samples))
#t))]))))))])))
(define (receive-available-frames! self)
(let ((dec (fmpg-instance-decoder self)) (produced 0))
(let loop ()
(let ((ret (avcodec_receive_frame (ds-codec-ctx dec) (ds-frame dec))))
(cond
[(= ret AVERROR_EAGAIN) produced]
[(= ret AVERROR_EOF) (ds-drained! dec #t) produced]
[(< ret 0) -1]
[else
(let ((ok? (append-converted-frame! self (ds-frame dec))))
(av_frame_unref (ds-frame dec))
(if ok?
(begin
(when (> (ds-last-samples dec) 0) (set! produced 1))
(loop))
-1))])))))
(define (read-selected-audio-packet! self pkt)
(let ((wanted-stream (ais-stream-index (fmpg-instance-audio-info self))))
(let loop ()
(let ((ret (av_read_frame (fmpg-instance-format-ctx self) pkt)))
(cond
[(< ret 0) #f]
[(= (avpacket-stream-index pkt) wanted-stream) #t]
[else
(av_packet_unref pkt)
(loop)])))))
(define/return (drain-resampler! self)
(let* ((dec (fmpg-instance-decoder self))
(info (fmpg-instance-audio-info self))
(channels (ais-channels info))
(sample-rate (ais-rate info)))
(let loop ((produced 0))
(let ((delay (swr_get_delay (ds-swr-ctx dec) sample-rate)))
(when (<= delay 0) (return produced))
(let ((max-bytes (av_samples_get_buffer_size #f channels delay FMPG_OUTPUT_FMT 1)))
(when (<= max-bytes 0) (return produced))
(let* ((tmp (malloc max-bytes 'raw))
(out-planes (malloc _pointer 1 'raw))
(finish (λ (v)
(when (not (eq? out-planes #f)) (free out-planes))
(when (not (eq? tmp #f)) (free tmp))
(return v)))
)
(when (or (eq? tmp #f) (eq? out-planes #f)) (finish -1))
(ptr-set! out-planes _pointer 0 tmp)
(let ((out-samples (swr_convert (ds-swr-ctx dec) out-planes delay #f 0)))
(when (<= out-samples 0) (finish produced))
(let ((used-bytes (av_samples_get_buffer_size #f channels out-samples FMPG_OUTPUT_FMT 1)))
(when (< used-bytes 0) (finish produced))
(when (pcm-empty? dec)
(ds-start-sample! dec (ds-next-sample-pos dec))
(ds-timecode! dec (/ (exact->inexact (ds-start-sample dec))
(exact->inexact sample-rate))))
(when (not (append-bytes! dec tmp used-bytes)) (finish -1))
(ds-last-samples! dec (+ (ds-last-samples dec) out-samples))
(ds-next-sample-pos! dec (+ (ds-next-sample-pos dec) out-samples))
(free out-planes)
(free tmp)
(loop 1)))))))))
(define (receive-or-return! self dec)
(let ((produced (receive-available-frames! self)))
(cond
[(< produced 0) (return 0)]
[(produced-pcm? produced dec) (return 1)]
[else produced])))
(define/return (fmpg-decode-next! instance)
(when (not (instance-ready? instance)) (return 0))
(let ((dec (fmpg-instance-decoder instance)))
(ds-clear-output! dec)
(receive-or-return! instance dec)
(let* ((pkt (av_packet_alloc))
(finish (λ (v)
(av_packet_free pkt)
(return v))))
(when (eq? pkt #f) (return 0))
(let loop ()
(unless (ds-eof-seen dec)
(cond
[(not (read-selected-audio-packet! instance pkt))
(ds-eof-seen! dec #t)
(av_packet_unref pkt)]
[else
(let ((ret (avcodec_send_packet (ds-codec-ctx dec) pkt)))
(av_packet_unref pkt)
(cond
[(= ret AVERROR_EAGAIN)
(receive-or-return! instance dec)
(loop)]
[(< ret 0) (finish 0)]
[else
(receive-or-return! instance dec)
(loop)]))])))
(av_packet_free pkt)
(unless (ds-drained dec)
(let ((ret (avcodec_send_packet (ds-codec-ctx dec) #f)))
(when (and (< ret 0) (not (= ret AVERROR_EOF))) (return 0)))
(receive-or-return! instance dec))
(let ((produced (drain-resampler! instance)))
(if (produced-pcm? produced dec) 1 0)))))
(define/return (fmpg-seek-ms! instance target-pos-ms)
(when (or (not (instance-ready? instance)) (< target-pos-ms 0)) (return 0))
(let/assert
((info (fmpg-instance-audio-info instance))
(dec (fmpg-instance-decoder instance))
(ctx (fmpg-instance-format-ctx instance))
(stream-index (ais-stream-index info))
(stream (avformat_stream ctx stream-index) a-!nullptr? 0)
(pos-us (av_rescale target-pos-ms AV_TIME_BASE 1000))
(stream-ts (av_rescale_q pos-us AV_TIME_BASE_Q (avstream-time_base stream)))
(ret-seek (av_seek_frame ctx stream-index stream-ts AVSEEK_FLAG_BACKWARD) (a->=? 0) 0)
(target-samples (samples_from_seconds (/ target-pos-ms 1000.0) (ais-rate info))))
(avcodec_flush_buffers (ds-codec-ctx dec))
(swr_close (ds-swr-ctx dec))
(let/assert
((ret-swr (swr_init (ds-swr-ctx dec)) (a->=? 0) 0))
(let ((pos (if (>= target-samples 0) target-samples 0)))
(ds-pcm! dec (make-bytes 0))
(ds-last-samples! dec 0)
(ds-start-sample! dec pos)
(ds-next-sample-pos! dec pos)
(ds-discard-until! dec target-samples)
(ds-timecode! dec (/ target-pos-ms 1000.0))
(ds-eof-seen! dec #f)
(ds-drained! dec #f)
1))))
(define (fmpg-decoder instance)
(and instance (fmpg-instance-decoder instance)))
(define (fmpg-buffer instance)
(let ((dec (fmpg-decoder instance)))
(if (and dec (not (pcm-empty? dec))) (ds-pcm dec) #f)))
(define (fmpg-buffer-size instance)
(let ((dec (fmpg-decoder instance)))
(if dec
(let ((n (bytes-length (ds-pcm dec))))
(if (> n INT_MAX) 0 n))
0)))
(define (fmpg-buffer-samples instance)
(let ((dec (fmpg-decoder instance)))
(if dec (ds-last-samples dec) 0)))
(define (fmpg-buffer-start-sample instance)
(let ((dec (fmpg-decoder instance)))
(if dec (ds-start-sample dec) 0)))
(define (fmpg-buffer-end-sample instance)
(let ((dec (fmpg-decoder instance)))
(if dec (+ (ds-start-sample dec) (ds-last-samples dec)) 0)))
(define (fmpg-sample-position instance)
(let ((dec (fmpg-decoder instance)))
(if dec (ds-next-sample-pos dec) 0)))
(define (fmpg-timecode instance)
(let ((dec (fmpg-decoder instance)))
(if dec (ds-timecode dec) 0.0)))
(define (fmpg-file-bitrate instance)
(let ((ctx (and instance (fmpg-instance-format-ctx instance))))
(if ctx
(let ((br (avformat_bit_rate ctx)))
(if (> br 0) br -1))
-1)))