Files
2020-07-01 10:43:10 +08:00

169 lines
5.4 KiB
Swift

//
// demuxing_decoding.swift
// SwiftFFmpegExamples
//
// Created by sunlubo on 2018/7/5.
//
import Foundation
import SwiftFFmpeg
private func openCodecContext(fmtCtx: AVFormatContext, mediaType: AVMediaType) throws -> (
AVCodecContext, Int
) {
let streamIndex = fmtCtx.findBestStream(type: mediaType)!
let stream = fmtCtx.streams[streamIndex]
// find decoder for the stream
let decoder = AVCodec.findDecoderById(stream.codecParameters.codecId)!
// Allocate a codec context for the decoder
let codecCtx = AVCodecContext(codec: decoder)
// Copy codec parameters from input stream to output codec context
codecCtx.setParameters(stream.codecParameters)
// Init the decoders, with or without reference counting
try codecCtx.openCodec(options: ["refcounted_frames": "1"])
return (codecCtx, streamIndex)
}
private func decode_video_packet(
codecCtx: AVCodecContext,
pkt: AVPacket?,
frame: AVFrame,
image: AVImage,
file: UnsafeMutablePointer<FILE>
) throws {
try codecCtx.sendPacket(pkt)
while true {
do {
try codecCtx.receiveFrame(frame)
} catch let err as AVError where err == .tryAgain || err == .eof {
break
}
if frame.width != image.width || frame.height != image.height
|| frame.pixelFormat != image.pixelFormat
{
fatalError(
"""
Error: Width, height and pixel format have to be constant in a rawvideo file,
but the width, height or pixel format of the input video changed:\n
old: width = \(image.width), height = \(image.height), format = \(image.pixelFormat)\n
new: width = \(frame.width), height = \(frame.height), format = \(frame.pixelFormat)
""")
}
print("video frame: \(codecCtx.frameNumber)")
// copy decoded frame to destination buffer:
// this is required since rawvideo expects non aligned data
image.copy(from: frame)
// write to rawvideo file
fwrite(image.data[0], 1, image.size, file)
frame.unref()
}
}
private func decode_audio_packet(
codecCtx: AVCodecContext,
pkt: AVPacket?,
frame: AVFrame,
file: UnsafeMutablePointer<FILE>
) throws {
try codecCtx.sendPacket(pkt)
while true {
do {
try codecCtx.receiveFrame(frame)
} catch let err as AVError where err == .tryAgain || err == .eof {
break
}
// Write the raw audio data samples of the first plane. This works
// fine for packed formats (e.g. AV_SAMPLE_FMT_S16). However,
// most audio decoders output planar audio, which uses a separate
// plane of audio samples for each channel (e.g. AV_SAMPLE_FMT_S16P).
// In other words, this code will write only the first audio channel
// in these cases.
// You should use libswresample or libavfilter to convert the frame
// to packed data.
let unpaddedLinesize = frame.sampleCount * frame.sampleFormat.bytesPerSample
fwrite(frame.extendedData[0], 1, unpaddedLinesize, file)
print("audio frame: \(codecCtx.frameNumber)")
frame.unref()
}
}
func demuxing_decoding() throws {
if CommandLine.argc < 4 {
print(
"Usage: \(CommandLine.arguments[0]) \(CommandLine.arguments[1]) input_file video_output_file audio_output_file"
)
return
}
let input = CommandLine.arguments[2]
let videoOutput = CommandLine.arguments[3]
let audioOutput = CommandLine.arguments[4]
// open input file, and allocate format context
let fmtCtx = try AVFormatContext(url: input)
// retrieve stream information
try fmtCtx.findStreamInfo()
// dump input information to stderr
fmtCtx.dumpFormat(isOutput: false)
let (videoCodecCtx, videoIndex) = try openCodecContext(fmtCtx: fmtCtx, mediaType: .video)
guard let videoOutputFile = fopen(videoOutput, "wb") else {
fatalError("Could not open \(videoOutput)")
}
defer { fclose(videoOutputFile) }
// allocate image where the decoded image will be put
let image = AVImage(
width: videoCodecCtx.width, height: videoCodecCtx.height, pixelFormat: videoCodecCtx.pixelFormat
)
let (audioCodecCtx, audioIndex) = try openCodecContext(fmtCtx: fmtCtx, mediaType: .audio)
guard let audioOutputFile = fopen(audioOutput, "wb") else {
fatalError("Could not open \(audioOutput)")
}
defer { fclose(audioOutputFile) }
print("Demuxing video from file '\(input)' into '\(videoOutput)'")
print("Demuxing audio from file '\(input)' into '\(audioOutput)'")
let frame = AVFrame()
let pkt = AVPacket()
// read frames from the file
while let _ = try? fmtCtx.readFrame(into: pkt) {
if pkt.streamIndex == videoIndex {
try decode_video_packet(
codecCtx: videoCodecCtx, pkt: pkt, frame: frame, image: image, file: videoOutputFile)
} else if pkt.streamIndex == audioIndex {
try decode_audio_packet(
codecCtx: audioCodecCtx, pkt: pkt, frame: frame, file: audioOutputFile)
}
pkt.unref()
}
// flush cached frames
try decode_video_packet(
codecCtx: videoCodecCtx, pkt: nil, frame: frame, image: image, file: videoOutputFile)
try decode_audio_packet(codecCtx: audioCodecCtx, pkt: nil, frame: frame, file: audioOutputFile)
print("Demuxing succeeded.")
print("Play the output video file with the command:")
print(
"ffplay -f rawvideo -pix_fmt \(videoCodecCtx.pixelFormat) -video_size \(videoCodecCtx.width)x\(videoCodecCtx.height) \(videoOutput)"
)
print("Play the output audio file with the command:")
// todo: audio format
print("ffplay -f f32le -ac 1 -ar \(audioCodecCtx.sampleRate) \(audioOutput)")
}