Skip to content

Commit

Permalink
FFmpeg code refactoring (codec context)
Browse files Browse the repository at this point in the history
  • Loading branch information
kartik-venugopal committed Oct 9, 2023
1 parent 2a17ac6 commit a897526
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 108 deletions.
Binary file not shown.
3 changes: 1 addition & 2 deletions Source/FFmpeg/Utils/FFmpegDecoder+PCMBuffer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,13 @@ extension FFmpegDecoder {
// Temporarily bind the input sample buffers as floating point numbers, and perform the copy.
frame.dataPointers.withMemoryRebound(to: UnsafeMutablePointer<Float>.self, capacity: channelCount) {srcPointers in

let sampleCount = frame.sampleCount
let firstSampleIndex = Int(frame.firstSampleIndex)

// Iterate through all the channels.
for channelIndex in 0..<channelCount {

// Use Accelerate to perform the copy optimally, starting at the given offset.
cblas_scopy(sampleCount,
cblas_scopy(frame.sampleCount,
srcPointers[channelIndex].advanced(by: firstSampleIndex), 1,
destPointers[channelIndex].advanced(by: sampleCountSoFar), 1)
}
Expand Down
67 changes: 33 additions & 34 deletions Source/FFmpeg/Utils/FFmpegPacketFrames.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,46 +70,45 @@ class FFmpegPacketFrames {
func keepLastNSamples(sampleCount: Int32) {

// Desired sample count must be less than existing sample count.
if sampleCount < self.sampleCount {

// Counter to keep track of samples accounted for so far.
var samplesSoFar: Int32 = 0
guard sampleCount < self.sampleCount else {return}

// Counter to keep track of samples accounted for so far.
var samplesSoFar: Int32 = 0

// Index of the first frame in the array that will not be removed.
var firstFrameToKeep: Int = 0

// Iterate the frames in reverse, counting the accumulated samples till we have enough.
for (index, frame) in frames.enumerated().reversed() {

// Index of the first frame in the array that will not be removed.
var firstFrameToKeep: Int = 0

// Iterate the frames in reverse, counting the accumulated samples till we have enough.
for (index, frame) in frames.enumerated().reversed() {
if samplesSoFar + frame.sampleCount <= sampleCount {

// This frame fits in its entirety.
samplesSoFar += frame.sampleCount

if samplesSoFar + frame.sampleCount <= sampleCount {

// This frame fits in its entirety.
samplesSoFar += frame.sampleCount

} else {

// This frame fits partially. Need to truncate it.
let samplesToKeep = sampleCount - samplesSoFar
samplesSoFar += samplesToKeep
frame.keepLastNSamples(sampleCount: samplesToKeep)
}
} else {

if samplesSoFar == sampleCount {

// We have enough samples. Note down the index of this frame,
// and exit the loop.
firstFrameToKeep = index
break
}
// This frame fits partially. Need to truncate it.
let samplesToKeep = sampleCount - samplesSoFar
samplesSoFar += samplesToKeep
frame.keepLastNSamples(sampleCount: samplesToKeep)
}

// Discard any surplus frames from the beginning of the array.
if firstFrameToKeep > 0 {
frames.removeFirst(firstFrameToKeep)
if samplesSoFar == sampleCount {

// We have enough samples. Note down the index of this frame,
// and exit the loop.
firstFrameToKeep = index
break
}

// Update the sample count.
self.sampleCount = sampleCount
}

// Discard any surplus frames from the beginning of the array.
if firstFrameToKeep > 0 {
frames.removeFirst(firstFrameToKeep)
}

// Update the sample count.
self.sampleCount = sampleCount
}
}
15 changes: 5 additions & 10 deletions Source/FFmpeg/Utils/FFmpegPacketTable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,13 @@ class FFmpegPacketTable {

// Keep reading packets till EOF is encountered.

while true {
while let packet = try fileContext.readPacket(from: stream) {

let packet = try FFmpegPacket(readingFromFormat: fileContext.pointer)
// Store a reference to this packet as the last packet encountered so far.
lastPacket = packet

if packet.streamIndex == stream.index {

// Store a reference to this packet as the last packet encountered so far.
lastPacket = packet

// Store byte position and timestamp info for this packet.
packetTable.append(FFmpegPacketTableEntry(bytePosition: packet.bytePosition, pts: packet.pts))
}
// Store byte position and timestamp info for this packet.
packetTable.append(FFmpegPacketTableEntry(bytePosition: packet.bytePosition, pts: packet.pts))
}

} catch {
Expand Down
22 changes: 9 additions & 13 deletions Source/FFmpeg/Wrappers/Decoding/FFmpegAudioCodec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ class FFmpegAudioCodec: FFmpegCodec {
self.channelLayout = FFmpegChannelLayout(id: context.channelLayout, channelCount: context.channels)

// Use multithreading to speed up decoding.
self.context.pointer.pointee.thread_count = Self.threadCount
self.context.pointer.pointee.thread_type = Self.threadType
self.context.threadCount = Self.threadCount
self.context.threadType = Self.threadType
}

override func open() throws {
Expand All @@ -99,7 +99,7 @@ class FFmpegAudioCodec: FFmpegCodec {
func decode(packet: FFmpegPacket) throws -> FFmpegPacketFrames {

// Send the packet to the decoder for decoding.
let resultCode: ResultCode = packet.sendToCodec(withContext: context.pointer)
let resultCode: ResultCode = context.sendPacket(packet)

// If the packet send failed, log a message and throw an error.
if resultCode.isNegative {
Expand All @@ -112,7 +112,7 @@ class FFmpegAudioCodec: FFmpegCodec {
let packetFrames: FFmpegPacketFrames = FFmpegPacketFrames()

// Keep receiving decoded frames while no errors are encountered
while let frame = FFmpegFrame(readingFrom: context.pointer, withSampleFormat: self.sampleFormat) {
while let frame = context.receiveFrame() {
packetFrames.appendFrame(frame)
}

Expand All @@ -132,14 +132,10 @@ class FFmpegAudioCodec: FFmpegCodec {
func decodeAndDrop(packet: FFmpegPacket) {

// Send the packet to the decoder for decoding.
var resultCode: ResultCode = packet.sendToCodec(withContext: context.pointer)
var resultCode: ResultCode = context.sendPacket(packet)
if resultCode.isNegative {return}

var avFrame: AVFrame = AVFrame()

repeat {
resultCode = avcodec_receive_frame(context.pointer, &avFrame)
} while resultCode.isZero && avFrame.nb_samples > 0
context.receiveAndDropAllFrames()
}

///
Expand All @@ -154,7 +150,7 @@ class FFmpegAudioCodec: FFmpegCodec {
func drain() throws -> FFmpegPacketFrames {

// Send the "flush packet" to the decoder
let resultCode: Int32 = avcodec_send_packet(context.pointer, nil)
let resultCode: Int32 = context.sendFlushPacket()

if resultCode.isNonZero {

Expand All @@ -166,7 +162,7 @@ class FFmpegAudioCodec: FFmpegCodec {
let packetFrames: FFmpegPacketFrames = FFmpegPacketFrames()

// Keep receiving decoded frames while no errors are encountered
while let frame = FFmpegFrame(readingFrom: context.pointer, withSampleFormat: self.sampleFormat) {
while let frame = context.receiveFrame() {
packetFrames.appendFrame(frame)
}

Expand All @@ -179,7 +175,7 @@ class FFmpegAudioCodec: FFmpegCodec {
/// Make sure to call this function prior to seeking within a stream.
///
func flushBuffers() {
avcodec_flush_buffers(context.pointer)
context.flushBuffers()
}

#if DEBUG
Expand Down
55 changes: 55 additions & 0 deletions Source/FFmpeg/Wrappers/Decoding/FFmpegCodecContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,61 @@ class FFmpegCodecContext {
}
}

func sendPacket(_ packet: FFmpegPacket) -> ResultCode {
avcodec_send_packet(pointer, &packet.avPacket)
}

func sendFlushPacket() -> ResultCode {
avcodec_send_packet(pointer, nil)
}

///
/// Instantiates a Frame, reading an AVFrame from this codec context, and sets its sample format.
///
func receiveFrame() -> FFmpegFrame? {

// Allocate memory for the frame.
var framePtr: UnsafeMutablePointer<AVFrame>! = av_frame_alloc()

// Check if memory allocation was successful. Can't proceed otherwise.
guard framePtr != nil else {

NSLog("Unable to allocate memory for frame.")
return nil
}

// Receive the frame from the codec context.
guard avcodec_receive_frame(pointer, framePtr).isNonNegative else {

av_frame_free(&framePtr)
return nil
}

return FFmpegFrame(encapsulatingPointeeOf: framePtr, withSampleFormat: FFmpegSampleFormat(encapsulating: self.sampleFormat))
}

///
/// Decode the current packet and drop (ignore) the received frames.
///
func receiveAndDropAllFrames() {

var avFrame: AVFrame = AVFrame()
var resultCode: ResultCode = 0

repeat {
resultCode = avcodec_receive_frame(pointer, &avFrame)
} while resultCode.isZero && avFrame.nb_samples > 0
}

///
/// Flush this codec's internal buffers.
///
/// Make sure to call this function prior to seeking within a stream.
///
func flushBuffers() {
avcodec_flush_buffers(pointer)
}

/// When this object is deinitialized, make sure that its allocated memory space is deallocated.
deinit {
avcodec_free_context(&pointer)
Expand Down
19 changes: 2 additions & 17 deletions Source/FFmpeg/Wrappers/Decoding/FFmpegFrame.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,25 +171,10 @@ class FFmpegFrame {
///
/// - Parameter sampleFormat: The format of the samples in this frame.
///
init?(readingFrom codecCtx: UnsafeMutablePointer<AVCodecContext>, withSampleFormat sampleFormat: FFmpegSampleFormat) {
init(encapsulatingPointeeOf pointer: UnsafeMutablePointer<AVFrame>!, withSampleFormat sampleFormat: FFmpegSampleFormat) {

// Allocate memory for the frame.
self.pointer = av_frame_alloc()

// Check if memory allocation was successful. Can't proceed otherwise.
guard pointer != nil else {

NSLog("Unable to allocate memory for frame.")
return nil
}

// Receive the frame from the codec context.
guard avcodec_receive_frame(codecCtx, pointer).isNonNegative else {

av_frame_free(&pointer)
return nil
}

self.pointer = pointer
self.sampleFormat = sampleFormat
self.firstSampleIndex = 0
}
Expand Down
2 changes: 1 addition & 1 deletion Source/FFmpeg/Wrappers/Demuxing/FFmpegAudioStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class FFmpegAudioStream: FFmpegStreamProtocol {
///
/// - Parameter mediaType: The media type of this stream (e.g. audio / video, etc)
///
init(encapsulating pointer: UnsafeMutablePointer<AVStream>) {
init(encapsulatingPointeeOf pointer: UnsafeMutablePointer<AVStream>) {

self.pointer = pointer
self.index = pointer.pointee.index
Expand Down
22 changes: 19 additions & 3 deletions Source/FFmpeg/Wrappers/Demuxing/FFmpegFileContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,9 @@ class FFmpegFileContext {

switch mediaType {

case AVMEDIA_TYPE_AUDIO: return FFmpegAudioStream(encapsulating: streamPointer)
case AVMEDIA_TYPE_AUDIO: return FFmpegAudioStream(encapsulatingPointeeOf: streamPointer)

case AVMEDIA_TYPE_VIDEO: return FFmpegImageStream(encapsulating: streamPointer)
case AVMEDIA_TYPE_VIDEO: return FFmpegImageStream(encapsulatingPointeeOf: streamPointer)

default: return nil

Expand All @@ -237,7 +237,23 @@ class FFmpegFileContext {
///
func readPacket(from stream: FFmpegStreamProtocol) throws -> FFmpegPacket? {

let packet = try FFmpegPacket(readingFromFormat: pointer)
var avPacket = AVPacket()

// Try to read a packet.
let readResult: Int32 = av_read_frame(pointer, &avPacket)

// If the read fails, log a message and throw an error.
guard readResult >= 0 else {

// No need to log a message for EOF as it is considered harmless.
if !readResult.isEOF {
NSLog("Unable to read packet. Error: \(readResult) (\(readResult.errorDescription)))")
}

throw PacketReadError(readResult)
}

let packet = FFmpegPacket(encapsulating: avPacket)
return packet.streamIndex == stream.index ? packet : nil
}

Expand Down
4 changes: 2 additions & 2 deletions Source/FFmpeg/Wrappers/Demuxing/FFmpegImageStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class FFmpegImageStream: FFmpegStreamProtocol {
/// The packet (optionally) containing an attached picture.
/// This can be used to read cover art.
///
private(set) lazy var attachedPic: FFmpegPacket = FFmpegPacket(encapsulating: &pointer.pointee.attached_pic)
private(set) lazy var attachedPic: FFmpegPacket = FFmpegPacket(encapsulatingPointeeOf: &pointer.pointee.attached_pic)

///
/// All metadata key / value pairs available for this stream.
Expand All @@ -55,7 +55,7 @@ class FFmpegImageStream: FFmpegStreamProtocol {
///
/// - Parameter mediaType: The media type of this stream (e.g. audio / video, etc)
///
init(encapsulating pointer: UnsafeMutablePointer<AVStream>) {
init(encapsulatingPointeeOf pointer: UnsafeMutablePointer<AVStream>) {

self.pointer = pointer
self.index = pointer.pointee.index
Expand Down
Loading

0 comments on commit a897526

Please sign in to comment.