JavaCV:avformat_open_input()挂起(不是网络,但使用自定义AVIOContext)

时间:2015-10-13 17:53:55

标签: ffmpeg javacv

我使用自定义AVIOContext将FFMpeg与java IO联系起来。函数avformat_open_input()永远不会返回。我在网上搜索了类似的问题,所有这些都是由网络故障或服务器配置错误引起的。但是,我根本没有使用网络,你可以在下面的小程序中看到:

package com.example;

import org.bytedeco.javacpp.*;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import static org.bytedeco.javacpp.avcodec.*;
import static org.bytedeco.javacpp.avformat.*;
import static org.bytedeco.javacpp.avutil.*;
import static org.bytedeco.javacpp.avdevice.*;
import static org.bytedeco.javacpp.avformat.AVFormatContext.*;

public class Test {

    public static void main(String[] args) throws Exception {
        File dir = new File(System.getProperty("user.home"), "Desktop");
        File file = new File(dir, "sample.3gp");
        final RandomAccessFile raf = new RandomAccessFile(file, "r");

        Loader.load(avcodec.class);
        Loader.load(avformat.class);
        Loader.load(avutil.class);
        Loader.load(avdevice.class);
        Loader.load(swscale.class);
        Loader.load(swresample.class);

        avcodec_register_all();
        av_register_all();
        avformat_network_init();
        avdevice_register_all();

        Read_packet_Pointer_BytePointer_int reader = new Read_packet_Pointer_BytePointer_int() {
            @Override
            public int call(Pointer pointer, BytePointer buf, int bufSize) {
                try {
                    byte[] data = new byte[bufSize]; // this is inefficient, just use as a quick example
                    int read = raf.read(data);

                    if (read <= 0) {
                        System.out.println("EOF found.");
                        return AVERROR_EOF;
                    }

                    System.out.println("Successfully read " + read + " bytes of data.");
                    buf.position(0);
                    buf.put(data, 0, read);
                    return read;
                } catch (Exception ex) {
                    ex.printStackTrace();
                    return -1;
                }
            }
        };

        Seek_Pointer_long_int seeker = new Seek_Pointer_long_int() {
            @Override
            public long call(Pointer pointer, long offset, int whence) {
                try {
                    raf.seek(offset);
                    System.out.println("Successfully seeked to position " + offset + ".");
                    return offset;
                } catch (IOException ex) {
                    return -1;
                }
            }
        };

        int inputBufferSize = 32768;
        BytePointer inputBuffer = new BytePointer(av_malloc(inputBufferSize));
        AVIOContext ioContext = avio_alloc_context(inputBuffer, inputBufferSize, 1, null, reader, null, seeker);
        AVInputFormat format = av_find_input_format("3gp");
        AVFormatContext formatContext = avformat_alloc_context();
        formatContext.iformat(format);
        formatContext.flags(formatContext.flags() | AVFMT_FLAG_CUSTOM_IO);
        formatContext.pb(ioContext);

        // This never returns. And I can never get result.
        int result = avformat_open_input(formatContext, "", format, null);

        // all clean-up code omitted for simplicity
    }

}

以下是我的示例控制台输出:

Successfully read 32768 bytes of data.
Successfully read 32768 bytes of data.
Successfully read 32768 bytes of data.
Successfully read 32768 bytes of data.
Successfully read 32768 bytes of data.
Successfully read 7240 bytes of data.
EOF found.

我检查了字节总和,它对应于文件大小; EOF也被击中,意味着文件被完全读取。实际上我有点怀疑为什么avformat_open_input()甚至会读取整个文件而仍然没有返回?我正在做的事情肯定有问题。任何专家都可以点亮或指出我正确的方向吗?我是javacvffmpeg的新手,尤其是使用Buffer和其他内容进行编程。欢迎任何帮助,建议或批评。提前谢谢。

2 个答案:

答案 0 :(得分:4)

好的,现在我发现了问题。我有误解文档和忽略我发现的大多数示例。我的坏。

根据ffmpeg的文件:

AVIOContext* avio_alloc_context (unsigned char* buffer,
                                 int            buffer_size,
                                 int            write_flag,
                                 void*          opaque,
                                 int(*)(void *opaque, uint8_t *buf, int buf_size)     read_packet,
                                 int(*)(void *opaque, uint8_t *buf, int buf_size)     write_packet,
                                 int64_t(*)(void *opaque, int64_t offset, int whence) seek 
)

第三个参数write_flag以下列方式使用:

write_flag - 如果缓冲区应该是可写的,则设置为1,否则设置为0

实际上,这意味着如果AVIOContext用于数据输出(即写作),write_flag应设置为1。否则,如果上下文用于数据输入(即读取),则应将其设置为0

在我发布的问题中,我将1作为write_flag传递,并且在阅读时导致问题。传递0代替了解决问题。

稍后我重新阅读了我找到的所有示例,所有avio_alloc_context()次调用都使用0,而不是1。这进一步说明了我遇到问题的原因。

总而言之,我将发布修订后的代码,并将纠正的问题作为未来参考。

package com.example;

import org.bytedeco.javacpp.*;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

import static org.bytedeco.javacpp.avformat.*;
import static org.bytedeco.javacpp.avutil.*;
import static org.bytedeco.javacpp.avformat.AVFormatContext.*;

public class Test {

    public static void main(String[] args) throws Exception {
        File dir = new File(System.getProperty("user.home"), "Desktop");
        File file = new File(dir, "sample.3gp");
        final RandomAccessFile raf = new RandomAccessFile(file, "r");

        Loader.load(avformat.class);
        Loader.load(avutil.class);

        av_register_all();
        avformat_network_init();

        Read_packet_Pointer_BytePointer_int reader = new Read_packet_Pointer_BytePointer_int() {
            @Override
            public int call(Pointer pointer, BytePointer buf, int bufSize) {
                try {
                    byte[] data = new byte[bufSize]; // this is inefficient, just use as a quick example
                    int read = raf.read(data);

                    if (read <= 0) {
                        // I am still unsure as to return '0', '-1' or 'AVERROR_EOF'.
                        // But according to the following link, it should return 'AVERROR_EOF',
                        // http://www.codeproject.com/Tips/489450/Creating-Custom-FFmpeg-IO-Context
                        // btw 'AVERROR_EOF' is a nasty negative number, '-541478725'.
                        return AVERROR_EOF;
                    }

                    buf.position(0);
                    buf.put(data, 0, read);
                    return read;
                } catch (Exception ex) {
                    ex.printStackTrace();
                    return -1;
                }
            }
        };

        Seek_Pointer_long_int seeker = new Seek_Pointer_long_int() {
            @Override
            public long call(Pointer pointer, long offset, int whence) {
                try {
                    if (whence == AVSEEK_SIZE) {
                        // Returns the entire file length. If not supported, simply returns a negative number.
                        // https://www.ffmpeg.org/doxygen/trunk/avio_8h.html#a427ff2a881637b47ee7d7f9e368be63f
                        return raf.length();
                    }

                    raf.seek(offset);
                    return offset;
                } catch (IOException ex) {
                    ex.printStackTrace();
                    return -1;
                }
            }
        };

        int inputBufferSize = 32768;
        BytePointer inputBuffer = new BytePointer(av_malloc(inputBufferSize));

        AVIOContext ioContext = avio_alloc_context(inputBuffer,
                                                   inputBufferSize,
                                                   0, // CRITICAL, if the context is for reading, it should be ZERO
                                                      //           if the context is for writing, then it is ONE
                                                   null,
                                                   reader,
                                                   null,
                                                   seeker);

        AVInputFormat format = av_find_input_format("3gp");
        AVFormatContext formatContext = avformat_alloc_context();
        formatContext.iformat(format);
        formatContext.flags(formatContext.flags() | AVFMT_FLAG_CUSTOM_IO);
        formatContext.pb(ioContext);

        // Now this is working properly.
        int result = avformat_open_input(formatContext, "", format, null);
        System.out.println("result == " + result);

        // all clean-up code omitted for simplicity
    }

}

参考文献:

  1. AVSEEK_SIZE documentation
  2. avio_alloc_context() documentation
  3. 其他参考资料:(我没有足够的声望点来获取更多链接,但我发现这些示例对我有帮助,所以我无论如何都用纯文本粘贴它们)

    1. 在以下位置创建自定义FFmpeg IO-Context(CodeProject示例): http://www.codeproject.com/Tips/489450/Creating-Custom-FFmpeg-IO-Context

    2. 另一个示例显示在write_flag中使用avio_alloc_context()https://www.ffmpeg.org/doxygen/2.5/avio_reading_8c-example.html#a20

答案 1 :(得分:1)

您的搜索代码需要处理AVSEEK_SIZE,并且您的读取应该在EOF上返回0(“在读取文件结尾时,返回零。” - 来自man 2的字面引用读取),而不是AVERROR_EOF。