//
//  encode_audio.swift
//  Demo
//
//  Created by sunlubo on 2018/7/3.
//

import Foundation
import SwiftFFmpeg

#if canImport(Darwin)
import Darwin
#else
import Glibc
#endif

private func encode(
  codecCtx: AVCodecContext,
  frame: AVFrame?,
  pkt: AVPacket,
  file: UnsafeMutablePointer<FILE>
) throws {
  // send the frame for encoding
  try codecCtx.sendFrame(frame)

  // read all the available output packets (in general there may be any number of them)
  while true {
    do {
      try codecCtx.receivePacket(pkt)
    } catch let err as AVError where err == .tryAgain || err == .eof {
      return
    }

    fwrite(pkt.data, 1, pkt.size, file)
    pkt.unref()
  }
}

func encode_audio() throws {
  if CommandLine.argc < 3 {
    print("Usage: \(CommandLine.arguments[0]) \(CommandLine.arguments[1]) output_file")
    return
  }

  let output = CommandLine.arguments[2]
  // find the MP2 encoder
  let codec = AVCodec.findEncoderById(.MP2)!
  let codecCtx = AVCodecContext(codec: codec)

  // set sample parameters
  codecCtx.bitRate = 64000
  codecCtx.sampleFormat = .int16
  codecCtx.sampleRate = 44100
  codecCtx.channelLayout = .CHL_STEREO
  codecCtx.channelCount = codecCtx.channelLayout.channelCount

  try codecCtx.openCodec()

  guard let file = fopen(output, "wb") else {
    fatalError("Could not open \(output)")
  }
  defer { fclose(file) }

  // packet for holding encoded output
  let pkt = AVPacket()

  // frame containing input raw audio
  let frame = AVFrame()
  frame.sampleCount = codecCtx.frameSize
  frame.sampleFormat = codecCtx.sampleFormat
  frame.channelLayout = codecCtx.channelLayout
  // allocate the data buffers
  try frame.allocBuffer()

  // encode a single tone sound
  var t = 0 as Float
  let tincr = Float(2 * Double.pi * 440 / Double(codecCtx.sampleRate))
  for _ in 0..<200 {
    // make sure the frame is writable -- makes a copy if the encoder kept a reference internally
    try frame.makeWritable()

    let capacity = frame.sampleCount * frame.channelCount
    let samples = UnsafeMutableRawPointer(frame.data[0]!).bindMemory(
      to: UInt16.self, capacity: capacity)
    for i in 0..<frame.sampleCount {
      let sample = UInt16(truncatingIfNeeded: Int32((sin(Double(t)) * 10000).rounded(.towardZero)))
      for j in 0..<frame.channelCount {
        samples[i * 2 + j] = sample
      }
      t += tincr
    }
    try encode(codecCtx: codecCtx, frame: frame, pkt: pkt, file: file)
  }

  // flush the encoder1
  try encode(codecCtx: codecCtx, frame: nil, pkt: pkt, file: file)
}
