实时解码安卓硬件编码的H264相机视频,使用ffmpeg。

28

我正在尝试在Android上使用硬件编码器 H264 来创建来自相机的视频,并使用FFmpeg混合音频(都在Android手机上完成)。

到目前为止,我已经将 H264 视频分成了 rtsp 数据包,并使用VLC (over UDP)进行解码,所以我知道视频格式至少是正确的。然而,我无法以 ffmpeg 能够理解的格式传递视频数据。

我尝试将相同的 rtsp 包发送到本地主机上的端口 5006(通过 UDP),然后提供 sdp 文件告诉 ffmpeg 视频流是从哪个本地端口进来的以及如何解码视频,如果我正确理解了 rtsp 流媒体协议的话。然而这并不起作用,我很难诊断原因,因为ffmpeg只是静静地等待输入。

由于延迟和可扩展性等原因,我不能仅仅将视频和音频发送到服务器并在那里进行混合,必须在手机上进行处理,而且要尽可能轻量级。

我想我需要的是关于如何实现这一目标的建议。最佳解决方案是将经过打包的 H264 视频通过管道发送到 ffmpeg,但这样我无法向ffmpeg发送sdp文件所需的参数来解码视频。

如果需要,我可以提供更多信息,例如有关如何为Android编译 ffmpeg,但我觉得这并不必要。

哦,我是通过命令行启动 ffmpeg 的方式运行它的,如果可能的话,我真的很希望避免操作JNI。

谢谢您的帮助。


3
为什么要使用ffmpeg解码?使用内置的MediaPlayer对象即可。 - Aviad Rozenhek
你尝试过使用live555通过RTSP流式传输ffmpeg的输出吗?此外,ffmpeg不应该探测流并自行查找流信息吗? - Shark
我认为Aviad说得很对。你怎么知道相机产生的视频格式是什么? - Edward Falk
内置的媒体播放器不会将视频打包进行流式传输。我当时尝试开发的应用程序是一个视频会议应用程序。实际上,录制视频的设备并不需要播放视频 - 思路是直接将其发送到FFMpeg,并使用FFMpeg作为一种可以混合原始音频样本和视频帧的媒体服务器 - 或者实时解密保存的视频文件,这似乎更加困难。 - joebobfrank
我猜 libstreaming 正好符合你的需求。不幸的是,时光机还没有通过 QA。 - Alex Cohn
2个回答

1
你尝试过使用 java.lang.Runtime 吗?
String[] parameters = {"ffmpeg", "other", "args"};
Program program Runtime.getRuntime().exec(parameters);

InputStream in = program.getInputStream();
OutputStream out = program.getOutputStream();
InputStream err = program.getErrorStream();

然后你可以将数据写入标准输出(stdout),并从标准输入(stdin)和标准错误(stderr)读取数据。这不是一个管道,但它应该比使用网络接口更好。


楼主在这里,我不再继续解决这个问题(我放弃了),但我应该补充说明我确实尝试过这个方法。我不记得所有的细节了,但我尝试使用InputStream和多个FIFO管道(一个用于视频,一个用于音频)使用操作系统。然而,这种方法的问题是我无法提供足够的信息给FFmpeg去理解和解码由摄像机生成的视频包。我想使用rtsp的真正原因是它 - 理论上 - 能够为FFmpeg提供足够的信息来解码实时流。 - joebobfrank

1
有点晚了,但我认为这是一个好问题,目前还没有很好的答案。
如果你想从Android设备流式传输相机和麦克风,你有两个主要选择:Java或NDK实现。
1. Java实现。
我只会提到这个想法,但基本上它是在Java中实现一个基于这些标准Real-Time Streaming Protocol Version 2.0RTP Payload Format for H.264 Video的RTSP服务器和RTP协议。这项任务将非常漫长和艰难。但如果你正在做你的PhP,那么拥有一个漂亮的Android RTSP Java库可能是不错的。
2. NDK实现。
这是另一种选择,包括各种解决方案。主要思路是在我们的Android应用程序中使用强大的C或C++库。例如,FFmpeg。该库可以编译为Android并支持各种架构。这种方法的问题是你可能需要学习Android NDK、C和C++才能完成这项任务。

但是有一种替代方案。您可以包装c库并使用FFmpeg。但是如何?

例如,使用已编译x264、libass、fontconfig、freetype和fribidi并支持各种体系结构的FFmpeg Android。但是,如果您想要实时流式传输,则仍然很难进行编程,因为您需要处理文件描述符和输入/输出流。

从Java编程角度来看,最好的选择是使用JavaCV。 JavaCV使用常用计算机视觉库的包装器,包括:(OpenCVFFmpeg等,并提供实用类以使它们在Java平台上更易于使用,包括(当然)Android。

JavaCV还配备了硬件加速的全屏图像显示(CanvasFrameGLCanvasFrame)、在多个核上并行执行代码的易于使用的方法(Parallel)、用户友好的相机和投影仪几何和颜色校准(GeometricCalibratorProCamGeometricCalibratorProCamColorCalibrator)、特征点检测和匹配(ObjectFinder)、实现投影仪-摄像头系统直接图像对齐的一组类(主要为GNImageAlignerProjectiveTransformerProjectiveColorTransformerProCamTransformerReflectanceInitializer)、一个斑点分析包(Blobs),以及在JavaCV类中的其他杂项功能。其中一些类还具有OpenCL和OpenGL对应物,它们的名称以CL结尾或以GL开头,例如:JavaCVCLGLCanvasFrame等。
但我们如何使用这个解决方案呢?
这里我们有一个基本实现来使用UDP进行流式传输。
String streamURL = "udp://ip_destination:port";
recorder = new FFmpegFrameRecorder(streamURL, frameWidth, frameHeight, 1);
recorder.setInterleaved(false);
// video options //
recorder.setFormat("mpegts");
recorder.setVideoOption("tune", "zerolatency");
recorder.setVideoOption("preset", "ultrafast");
recorder.setVideoBitrate(5 * 1024 * 1024);
recorder.setFrameRate(30);
recorder.setSampleRate(AUDIO_SAMPLE_RATE);
recorder.setVideoCodec(AV_CODEC_ID_H264);
recorder.setAudioCodec(AV_CODEC_ID_AAC);

这段代码演示了如何初始化名为“recorder”的FFmpegFrameRecorder对象。该对象将捕获和编码从相机和麦克风获取的帧和样本。
如果您想在同一Android应用程序中捕获预览,那么我们需要实现一个CameraPreview类,该类将转换从摄像机提供的原始数据,并创建预览和FFmpegFrameRecorder的帧。
请记住将ip_destination替换为要发送流的PC或设备的IP地址。端口可以是8080作为例子。
@Override
public Mat onCameraFrame(Mat mat)
{
    if (audioRecordRunnable == null) {
        startTime = System.currentTimeMillis();
        return mat;
    }
    if (recording && mat != null) {
        synchronized (semaphore) {
            try {
                Frame frame = converterToMat.convert(mat);
                long t = 1000 * (System.currentTimeMillis() - startTime);
                if (t > recorder.getTimestamp()) {
                    recorder.setTimestamp(t);
                }
                recorder.record(frame);
            } catch (FFmpegFrameRecorder.Exception e) {
                LogHelper.i(TAG, e.getMessage());
                e.printStackTrace();
            }
        }
    }
    return mat;
}

这个方法展示了onCameraFrame方法的实现,它从相机获取Mat(图片),将其转换为帧,并由FFmpegFrameRecorder对象记录。

@Override
public void onSampleReady(ShortBuffer audioData)
{
    if (recorder == null) return;
    if (recording && audioData == null) return;

    try {
        long t = 1000 * (System.currentTimeMillis() - startTime);
        if (t > recorder.getTimestamp()) {
            recorder.setTimestamp(t);
        }
        LogHelper.e(TAG, "audioData: " + audioData);
        recorder.recordSamples(audioData);
    } catch (FFmpegFrameRecorder.Exception e) {
        LogHelper.v(TAG, e.getMessage());
        e.printStackTrace();
    }
}

与音频一样,audioData是一个ShortBuffer对象,将由FFmpegFrameRecorder记录。

在PC或设备目标中,您可以运行以下命令以获取流。

ffplay udp://ip_source:port
ip_source 是智能手机流媒体摄像头和麦克风的IP地址。端口必须是相同的8080。
我在我的github存储库中创建了一个解决方案,链接在这里:UDPAVStreamer
祝好运

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接