I am trying to get Custom IO working with JavaCPP FFMPEG. Has anyone succeeded at this? Can you post your code?
Here is mine (Scala), which does not work. It compiles and runs, but does not seem to call my Read and Seek methods
val readPtr = new Pointer()
val seekPtr = new Pointer()
val userDataPtr = new Pointer()
val read = new Read_packet_Pointer_BytePointer_int(readPtr) {
override def call(userDataPtr: Pointer, @Cast(Array("uint8_t*")) buf: BytePointer, buf_size: Int): Int = {
super.call(userDataPtr, buf, buf_size)
}
}
val seek = new Seek_Pointer_long_int(seekPtr) {
@Cast(Array("int64_t")) override def call(userDataPtr: Pointer, @Cast(Array("int64_t")) offset: Long, whence: Int): Long = {
super.call(userDataPtr, offset, whence)
}
}
val bufferSize = 1024 * 1024
val readBuffer = new BytePointer(bufferSize)
val avioContext = avformat.avio_alloc_context(
readBuffer, // internal buffer
bufferSize, // and its size
0, // write flag (1=true,0=false)
userDataPtr, // user data, will be passed to our callback functions
read,
null, // no writing
seek
)
formatContext.pb(avioContext)
formatContext.flags(formatContext.flags | avformat.AVFormatContext.AVFMT_FLAG_CUSTOM_IO)
formatContext.iformat(avformat.av_find_input_format("mp4"))
avformat.avformat_open_input(formatContext, null.asInstanceOf[String], null, null)
After some effort, I finally got it. Here is my working code that exercises CustomerIO, Fifo, Format and Codec. I hope it saves some future developer a lot of time. Enjoy
import java.io.{FileInputStream, RandomAccessFile}
import java.nio.ByteBuffer
import org.bytedeco.javacpp.avformat._
import org.bytedeco.javacpp.avutil.AVDictionary
import org.bytedeco.javacpp._
import org.bytedeco.javacpp.annotation.Cast
import org.bytedeco.javacpp.avcodec.{AVCodecContext, AVPacket}
/**
* User: peter
* Date: 2/17/17
* Time: 7:47 PM
*/
object FFMEGTest2 extends App {
val AVERROR_NONE = 0
val AVERROR_EAGAIN = -11
val AVERROR_RESOURCE_NOT_AVAILABLE = -35
def chk( x: Int ) = {
if( x < 0 ) {
val errormsgBytes = new Array[Byte](1024)
avutil.av_strerror(x, errormsgBytes, 1024)
throw new Error(new String(errormsgBytes).trim)
}
x
}
def chkNull[T]( x: T ) = {
if(x==null) {
throw new Error("Unable to allocate object")
}
x
}
avformat.av_register_all()
val mp4 = "foo.mp4"
val ogg = "bah.ogg"
doit(mp4, "mp4")
doit(ogg, "ogg")
def doit( path: String, fmt: String ): Unit = {
val input = new RandomAccessFile(path,"r")
val data = new Array[Byte](input.length().toInt)
input.read(data, 0, data.length)
doit(data,fmt)
}
def doit( data: Array[Byte], fmt: String ) = {
val pointerPointer = new PointerPointer(1)
val lineSizePtr = new IntPointer(1L)
chk(avutil.av_samples_alloc(pointerPointer,lineSizePtr,1,4096,8,0))
var offset = 0
object ReadInput extends Read_packet_Pointer_BytePointer_int {
override def call(opaque: Pointer, buffer: BytePointer, buffer_size: Int): Int = {
try {
println(s"read ${offset} ${buffer_size}")
if (offset >= data.length) {
avutil.AVERROR_EOF
}
else {
val avail = data.length - offset
val l = if( avail < buffer_size ) avail else buffer_size
//Array.copy(data,offset,buffer,0,avail)
buffer.put(data, offset, l)
offset += l
l
}
}
catch {
case t: Throwable =>
throw new Error("Error on InputStream.read(): ", t)
}
}
}
object SeekInput extends Seek_Pointer_long_int {
@Cast(Array("int64_t")) override def call(userDataPtr: Pointer, @Cast(Array("int64_t")) where: Long, whence: Int): Long = {
/*
#define SEEK_SET 0 // Seek relative to begining of file
#define SEEK_CUR 1 // Seek relative to current file position
#define SEEK_END 2 // Seek relative to end of file
avformat.AVSEEK_SIZE
ORing this as the "whence" parameter to a seek function causes it to
return the filesize without seeking anywhere. Supporting this is optional.
If it is not supported then the seek function will return <0.
*/
try {
val whence2 = whence & ~avformat.AVSEEK_SIZE
whence2 match {
case 0 => offset = where.toInt
case 1 => offset = offset + where.toInt
case 2 => offset = data.length - where.toInt
case _ =>
throw new Error(s"Unknown whence on InputStream.seek(): ${where} ${whence2} ")
}
if( (whence & avformat.AVSEEK_SIZE) != 0) data.length else 0
} catch {
case t: Throwable =>
throw new Error("Error on InputStream.reset() or skip(): ", t)
}
}
}
val avioBufferSize = 4096
val avioBuffer = new BytePointer(avutil.av_malloc(avioBufferSize))
val formatContext = chkNull(avformat.avformat_alloc_context())
val avioContext = chkNull(avformat.avio_alloc_context(
avioBuffer,
avioBufferSize,
0,
formatContext,
ReadInput,
null,
SeekInput))
formatContext.pb(avioContext)
val inputFormat = chkNull(avformat.av_find_input_format(fmt))
formatContext.iformat(inputFormat)
chk(avformat.avformat_open_input(formatContext, "", null, null))
val audioStreams = (0 until formatContext.nb_streams) filter { i: Int =>
formatContext.streams(i).codec().codec_type() == avutil.AVMEDIA_TYPE_AUDIO
}
audioStreams.foreach { audioStream =>
val codecContext = formatContext.streams(audioStream).codec()
val decoder = chkNull(avcodec.avcodec_find_decoder(codecContext.codec_id))
chk(avcodec.avcodec_open2(codecContext, decoder, null.asInstanceOf[AVDictionary]))
val fifo = chkNull(avutil.av_audio_fifo_alloc(
8,
1,
1024*1024))
var more = true
while (more) {
val packet = new AVPacket
avcodec.av_init_packet(packet)
if (avformat.av_read_frame(formatContext, packet) == 0) {
chk(avcodec.avcodec_send_packet(codecContext, packet))
val frame = avutil.av_frame_alloc()
val errno = avcodec.avcodec_receive_frame(codecContext, frame)
errno match {
case avutil.AVERROR_EOF => more = false
// try again, read another packet
case AVERROR_EAGAIN =>
case AVERROR_RESOURCE_NOT_AVAILABLE =>
case AVERROR_NONE =>
chk(avutil.av_audio_fifo_write(fifo,frame.buf,frame.nb_samples))
println(s"${avutil.av_audio_fifo_size(fifo)} ${frame.nb_samples} ${frame.format} ${frame.best_effort_timestamp} ${frame.channels} ${frame.decode_error_flags} ${frame.flags} ${frame.sample_rate}")
if(avutil.av_audio_fifo_size(fifo) >= 4096) {
chk(avutil.av_audio_fifo_read(fifo,pointerPointer,4096))
val byteBuf = ByteBuffer wrap(new Array[Byte](4096*4))
chk(avutil.av_samples_copy(byteBuf,pointerPointer.get(0).asByteBuffer(),0,0,4096,1,8))
val samples = ByteBuffer.wrap(byteBuf.array()).asFloatBuffer()
}
case code: Int =>
chk(code)
}
avutil.av_frame_free(frame)
} else more = false
avcodec.av_packet_unref(packet)
}
avutil.av_audio_fifo_free(fifo)
avcodec.avcodec_close(codecContext)
}
avformat.avformat_close_input(formatContext)
}
}