【问题标题】:Android: streaming the camera as mjpegAndroid:将相机流式传输为 mjpeg
【发布时间】:2013-01-26 17:30:33
【问题描述】:

在 SO 和 google 搜索了几天后,我开始放弃了,所以我想我不妨在这里发帖。

我正在创建一个应该提供某种视频聊天的 Android 应用。由于这应该尽可能接近实时,我阅读了各种协议,并决定尝试 MJPEG 作为初学者(暂时不关心音频)。

现在流式传输数据让我抓狂。连接建立,应用程序开始将相机预览帧写入流,但 VLC 和 mplayer 都没有开始播放视频。监控连接显示数据正在到达。

正在连接 此代码由异步任务执行,成功通知侦听器:

try
    {
        ServerSocket server = new ServerSocket(8080);

        socket = server.accept();

        server.close();

        Log.i(TAG, "New connection to :" + socket.getInetAddress());

        stream = new DataOutputStream(socket.getOutputStream());
        prepared = true;
    }
    catch (IOException e)
    {
        Log.e(TAG, e.getMessage();
    }

在我的 PC 上,我执行“mplayer http://tabletIP:8080”并且平板电脑注册了一个连接(从而启动了我的流媒体和相机预览)。这也适用于 VLC。

流式传输这会将标头写入流:

if (stream != null)
{
    try
    {
        // send the header
        stream.write(("HTTP/1.0 200 OK\r\n" +
                      "Server: iRecon\r\n" +
                      "Connection: close\r\n" +
                      "Max-Age: 0\r\n" +
                      "Expires: 0\r\n" +
                      "Cache-Control: no-cache, private\r\n" + 
                      "Pragma: no-cache\r\n" + 
                      "Content-Type: multipart/x-mixed-replace; " +
                      "boundary=--" + boundary +
                      "\r\n\r\n").getBytes());

        stream.flush();

        streaming = true;
    }
    catch (IOException e)
    {
        notifyOnEncoderError(this, "Error while writing header: " + e.getMessage());
        stop();
    }
}

之后通过 Camera.onPreviewFrame() 回调触发流式传输:

@Override
public void onPreviewFrame(byte[] data, Camera camera)
{
    frame = data;

    if (streaming)
        mHandler.post(this);
}

@Override
public void run()
{
    // TODO: cache not filling?
    try
    {
                    // buffer is a ByteArrayOutputStream
        buffer.reset();

        switch (imageFormat)
        {
            case ImageFormat.JPEG:
                // nothing to do, leave it that way
                buffer.write(frame);
                break;

            case ImageFormat.NV16:
            case ImageFormat.NV21:
            case ImageFormat.YUY2:
            case ImageFormat.YV12:
                new YuvImage(frame, imageFormat, w, h, null).compressToJpeg(area, 100, buffer);
                break;

            default:
                throw new IOException("Error while encoding: unsupported image format");
        }

        buffer.flush();

        // write the content header
        stream.write(("--" + boundary + "\r\n" + 
                      "Content-type: image/jpg\r\n" + 
                      "Content-Length: " + buffer.size() + 
                      "\r\n\r\n").getBytes());

        // Should omit the array copy
        buffer.writeTo(stream);

        stream.write("\r\n\r\n".getBytes());
        stream.flush();
    }
    catch (IOException e)
    {
        stop();
        notifyOnEncoderError(this, e.getMessage());
    }
}

没有抛出异常。 mHandler 在它自己的 HandlerThread 中运行。只是为了确保我尝试使用 AsyncTask,但无济于事(顺便说一句,这更好吗?)。

编码帧在android端很好,我将它们保存为jpg文件并可以打开它们。

我的猜测是我必须以某种方式对数据进行聚类,或者必须为套接字或其他东西设置一些选项,但是....好吧,我被卡住了。

tl;dr: VLC 没有播放流,mplayer 说'缓存没有填充',问题可能在最后一个代码段,需要帮助~:)

谢谢!

【问题讨论】:

    标签: java android streaming mjpeg


    【解决方案1】:

    我明白了。好像我的 http-/content-headers 搞砸了。正确的标题应该是:

    stream.write(("HTTP/1.0 200 OK\r\n" +
                              "Server: iRecon\r\n" +
                              "Connection: close\r\n" +
                              "Max-Age: 0\r\n" +
                              "Expires: 0\r\n" +
                              "Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\r\n" +
                              "Pragma: no-cache\r\n" + 
                              "Content-Type: multipart/x-mixed-replace; " +
                              "boundary=" + boundary + "\r\n" +
                              "\r\n" +
                              "--" + boundary + "\r\n").getBytes());
    

    stream.write(("Content-type: image/jpeg\r\n" +
                          "Content-Length: " + buffer.size() + "\r\n" +
                          "X-Timestamp:" + timestamp + "\r\n" +
                          "\r\n").getBytes());
    
    buffer.writeTo(stream);
    stream.write(("\r\n--" + boundary + "\r\n").getBytes());
    

    当然,在哪里设置边界是你自己的选择。也可能有一些字段是可选的(例如大多数在 Cache-Control 中),但这是有效的,直到现在我都懒得把它们去掉。重要的部分是记住换行符 (\r\n thingies)...

    【讨论】:

    • @Sourav 你的类必须实现 Camera.PreviewCallback。在初始化您的相机调用 camera.setPreviewCallback(...) 之后。检索 JPEGS 的其余部分已经在我的问题中了。
    • 对于那些想知道边界应该是什么的人,它可以是任何 java 字符串(由字母组成,不确定数字和特殊字符是否有效)。
    • @Managarm 我将 rtsp 协议用于真正的流式安卓相机,但我在接收器处有大约 2 秒的延迟。我将协议更改为使用 mjpeg 流媒体,但在建立连接时视频并没有在接收器上开始播放,类似于您的问题。你能告诉我你是怎么解决这个问题的吗?
    • @HAMED 抱歉,这是很久以前的事了,我们最终使用了 RTSP(MJPEG 是我们丢弃的原始实现)。我能用代码告诉你的一切都在我的问题和我找到的答案中。确保在具有直接连接的本地网络上进行测试,以避免 NAT 问题。啊,不管你做什么,除非你不能,否则使用UDP。除此之外,我不希望 MJPEG 具有更低的延迟,它实际上应该更高,因为每一帧基本上都是一个关键帧。如果可以,请尝试使用现代编解码器,例如 h264。干杯!
    猜你喜欢
    • 2018-07-16
    • 1970-01-01
    • 2012-03-15
    • 1970-01-01
    • 2015-06-24
    • 2015-02-09
    • 1970-01-01
    • 2011-11-30
    • 2011-10-16
    相关资源
    最近更新 更多