我来自一个线程编码H.264从摄像头与Android媒体编解码器。我的设置很相似。然而,我尝试用javacv编写mux编码帧和并通过RTMP广播它们。
rtmpclient.java
...
private volatile BlockingQueue<byte[]> mFrameQueue = new LinkedBlockingQueue<byte[]>(MAXIMUM_VIDEO_FRAME_BACKLOG);
...
private void startStream() throws FrameRecorder.Exception, IOException {
if (TextUtils.isEmpty(mDestination)) {
throw new IllegalStateException("Cannot start RtmpClient without destination");
}
if (mCamera == null) {
throw new IllegalStateException("Cannot start RtmpClient without camera.");
}
Camera.Parameters cameraParams = mCamera.getParameters();
mRecorder = new FFmpegFrameRecorder(
mDestination,
mVideoQuality.resX,
mVideoQuality.resY,
(mAudioQuality.channelType.equals(AudioQuality.CHANNEL_TYPE_STEREO) ? 2 : 1));
mRecorder.setFormat("flv");
mRecorder.setFrameRate(mVideoQuality.frameRate);
mRecorder.setVideoBitrate(mVideoQuality.bitRate);
mRecorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
mRecorder.setSampleRate(mAudioQuality.samplingRate);
mRecorder.setAudioBitrate(mAudioQuality.bitRate);
mRecorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
mVideoStream = new VideoStream(mRecorder, mVideoQuality, mFrameQueue, mCamera);
mAudioStream = new AudioStream(mRecorder, mAudioQuality);
mRecorder.start();
// Setup a bufferred preview callback
setupCameraCallback(mCamera, mRtmpClient, DEFAULT_PREVIEW_CALLBACK_BUFFERS,
mVideoQuality.resX * mVideoQuality.resY * ImageFormat.getBitsPerPixel(
cameraParams.getPreviewFormat())/8);
try {
mVideoStream.start();
mAudioStream.start();
}
catch(Exception e) {
e.printStackTrace();
stopStream();
}
}
...
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
boolean frameQueued = false;
if (mRecorder == null || data == null) {
return;
}
frameQueued = mFrameQueue.offer(data);
// return the buffer to be reused - done in videostream
//camera.addCallbackBuffer(data);
}
...
...
@Override
public void run() {
try {
mMediaCodec = MediaCodec.createEncoderByType("video/avc");
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", mVideoQuality.resX, mVideoQuality.resY);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, mVideoQuality.bitRate);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, mVideoQuality.frameRate);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mMediaCodec.start();
}
catch(IOException e) {
e.printStackTrace();
}
long startTimestamp = System.currentTimeMillis();
long frameTimestamp = 0;
byte[] rawFrame = null;
try {
while (!Thread.interrupted()) {
rawFrame = mFrameQueue.take();
frameTimestamp = 1000 * (System.currentTimeMillis() - startTimestamp);
encodeFrame(rawFrame, frameTimestamp);
// return the buffer to be reused
mCamera.addCallbackBuffer(rawFrame);
}
}
catch (InterruptedException ignore) {
// ignore interrup while waiting
}
// Clean up video stream allocations
try {
mMediaCodec.stop();
mMediaCodec.release();
mOutputStream.flush();
mOutputStream.close();
} catch (Exception e){
e.printStackTrace();
}
}
...
private void encodeFrame(byte[] input, long timestamp) {
try {
ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();
int inputBufferIndex = mMediaCodec.dequeueInputBuffer(0);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(input);
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, timestamp, 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
if (outputBufferIndex >= 0) {
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
// Should this be a direct byte buffer?
byte[] outData = new byte[bufferInfo.size - bufferInfo.offset];
outputBuffer.get(outData);
mFrameRecorder.record(outData, bufferInfo.offset, outData.length, timestamp);
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
}
else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
outputBuffers = mMediaCodec.getOutputBuffers();
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// ignore for now
}
} catch (Throwable t) {
t.printStackTrace();
}
}
...
...
// Hackish codec copy frame recording function
public boolean record(byte[] encodedData, int offset, int length, long frameCount) throws Exception {
int ret;
if (encodedData == null) {
return false;
}
av_init_packet(video_pkt);
// this is why i wondered whether I should get outputbuffer data into direct byte buffer
video_outbuf.put(encodedData, 0, encodedData.length);
video_pkt.data(video_outbuf);
video_pkt.size(video_outbuf_size);
video_pkt.pts(frameCount);
video_pkt.dts(frameCount);
video_pkt.stream_index(video_st.index());
synchronized (oc) {
/* write the compressed frame in the media file */
if (interleaved && audio_st != null) {
if ((ret = av_interleaved_write_frame(oc, video_pkt)) < 0) {
throw new Exception("av_interleaved_write_frame() error " + ret + " while writing interleaved video frame.");
}
} else {
if ((ret = av_write_frame(oc, video_pkt)) < 0) {
throw new Exception("av_write_frame() error " + ret + " while writing video frame.");
}
}
}
return (video_pkt.flags() & AV_PKT_FLAG_KEY) == 1;
}
...
ffprobe version 2.5.3 Copyright (c) 2007-2015 the FFmpeg developers
built on Jan 19 2015 12:56:57 with gcc 4.1.2 (GCC) 20080704 (Red Hat 4.1.2-55)
configuration: --prefix=/usr --bindir=/usr/bin --datadir=/usr/share/ffmpeg --incdir=/usr/include/ffmpeg --libdir=/usr/lib64 --mandir=/usr/share/man --arch=x86_64 --optflags='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic' --enable-bzlib --disable-crystalhd --enable-libass --enable-libdc1394 --enable-libfaac --enable-nonfree --disable-indev=jack --enable-libfreetype --enable-libgsm --enable-libmp3lame --enable-openal --enable-libopencv --enable-libopenjpeg --enable-libopus --enable-librtmp --enable-libtheora --enable-libvorbis --enable-libvpx --enable-libx264 --enable-libxvid --enable-x11grab --enable-avfilter --enable-avresample --enable-postproc --enable-pthreads --disable-static --enable-shared --enable-gpl --disable-debug --disable-stripping --enable-libcaca --shlibdir=/usr/lib64 --enable-runtime-cpudetect
libavutil 54. 15.100 / 54. 15.100
libavcodec 56. 13.100 / 56. 13.100
libavformat 56. 15.102 / 56. 15.102
libavdevice 56. 3.100 / 56. 3.100
libavfilter 5. 2.103 / 5. 2.103
libavresample 2. 1. 0 / 2. 1. 0
libswscale 3. 1.101 / 3. 1.101
libswresample 1. 1.100 / 1. 1.100
libpostproc 53. 3.100 / 53. 3.100
Metadata:
Server NGINX RTMP (github.com/arut/nginx-rtmp-module)
width 320.00
height 240.00
displayWidth 320.00
displayHeight 240.00
duration 0.00
framerate 0.00
fps 0.00
videodatarate 261.00
videocodecid 7.00
audiodatarate 62.00
audiocodecid 10.00
profile
level
[live_flv @ 0x1edb0820] Could not find codec parameters for stream 0 (Video: none, none, 267 kb/s): unknown codec
Consider increasing the value for the 'analyzeduration' and 'probesize' options
Input #0, live_flv, from 'rtmp://<server>/input/<stream id>':
Metadata:
Server : NGINX RTMP (github.com/arut/nginx-rtmp-module)
displayWidth : 320
displayHeight : 240
fps : 0
profile :
level :
Duration: 00:00:00.00, start: 16.768000, bitrate: N/A
Stream #0:0: Video: none, none, 267 kb/s, 1k tbr, 1k tbn, 1k tbc
Stream #0:1: Audio: aac (LC), 16000 Hz, mono, fltp, 63 kb/s
Unsupported codec with id 0 for input stream 0
我不是,无论如何,一个专家在H264或视频编码。我知道从MediaCodec出来的编码帧包含SPS NAL、PPS NAL和帧NAL单元。我还将MediaCodec输出写入了一个文件,并且能够回放它(我确实必须指定格式和帧率,否则它会播放得太快)。
我的假设是事情应该工作(看看我知道的有多少:))。知道了SPS和PPS被写出来,解码器应该知道足够多。然而,ffprobe无法识别编解码器、fps和其他视频信息。我需要将包标志信息传递给FFMPEGFrameRecorder.java:record()函数吗?或者我应该使用直接缓冲区?任何建议都将被感激!我应该用一个提示来弄清楚事情。
PS:我知道有些编解码器使用平面和其他半平面颜色格式。如果我过了这一关,这种区别就会出现。此外,我没有使用Surface到MediaCodec的方式,因为我需要支持API17,它需要比这条路由更多的更改,我认为这有助于我理解更基本的流程。阿根,我很感激任何建议。如果有什么需要澄清的请告诉我。
更新#1
所以在做了更多的测试之后,我看到我的编码器输出以下帧:
000000016742800DDA0507E806D0A1350000000168CE06E2
0000000165B840A6F1E7F0EA24000AE73BEB5F51CC7000233A84240...
0000000141E2031364E387FD4F9BB3D67F51CC7000279B9F9CFE811...
0000000141E40304423FFFFF0B7867F89FAFFFFFFFFFFCBE8EF25E6...
0000000141E602899A3512EF8AEAD1379F0650CC3F905131504F839...
...
第一帧包含SP和PPS。据我所知,这些只有一次传送。其余为NAL类型1和5。因此,我的假设是,为了让ffprobe不仅在流开始时看到流信息,我应该捕获SPS和PPS帧,并在一定数量的帧之后,或者在每个I帧之前,定期地重新发送它们。你觉得呢?
据我所知,你不需要复用视频和音频流。当您发送公告消息时,您还可以指定将音频和视频流发送到哪个端口。您需要将它们单独打包并用RTP发送。更多信息见维基百科。https://en.wikipedia.org/wiki/real_time_streaming_protocol
我正在开发一个通过RTP接收H264编码数据的应用程序,但我无法让Android的MediaCodec输出任何内容。我正在按照https://stackoverflow.com/a/7668578/10788248对RTP数据包进行解包 在编码帧被重新组装后,我将它们输入到出列的输入缓冲区中。 当我对输入缓冲区进行排队时,我不会得到任何错误,但是解码器的回调从来不会调用onOutputBuffer
我的最终目标是对熊猫专栏进行热编码。在本例中,我想对一列“b”进行热编码,如下所示:保存苹果、香蕉和桔子,并将任何其他水果编码为“其他”。 示例:在下面的代码中,“葡萄柚”将被改写为“其他”,如果“猕猴桃”和“鳄梨”出现在我的数据中,它们也将被改写为“其他”。 以下代码有效: 我的问题是:有没有一种更短的方法来做业务?我尝试了
我知道使用Wireshark和VLC保存RTP h264流是可能的。但为了学习更多关于视频流,我正在尝试自己做。有几个相关的问题有助于阅读这个主题: 如何处理原始UDP数据包,以便directshow源筛选器中的解码器筛选器对其进行解码 如何将H.264 UDP数据包转换为可播放的媒体流或文件(碎片整理) 以这些为背景,我目前的工作如下: 我可以通过UTP接收RTP数据包。 我按照上面问题中的讨论
我阅读了H.264视频RFC的RTP有效载荷格式,如果我在视频流中发现有sps和pps数据包(元数据),然后是Idr(完整图像),然后在上一个Idr到当前状态之间改变数据包,再从一开始。 我知道每个包装h264数据的rtp包报头都有序列号。 我不明白的是,对于更改的数据包(在Idr数据包之间),他们如何知道与它们相关的每个Idr? 在h264报头/数据中是否有写入它们与哪个rtp序列号或h264序
2)当我接收到一个数据包,并且它是“fragment_type==28”(不仅仅是这样,而是让我们认为我真的得到了一个片段),这意味着我有一个IDR片段。 3)每个报文都有一个由发信方生成的序列号,该序列号按如下顺序排列:如果报文a的序列号为20,则发信方发送的下一个报文为21,以此类推。 现在让我们来回答我的问题: A)如果我有一个IDR要重建,我如何知道什么数据包属于这个IDR?让我举一个例子
我正在解码从Android上的wifi摄像头接收到的原始h264。 这是解码时产生的视频的一个例子,除了底部部分看起来很好。 我还注意到一些奇怪的事情,当我移动摄像机时,饲料似乎运行几乎完全流畅(底部没有垃圾),一旦我把它放下,垃圾视频返回(我会以为它是相反的方式...) 我正在将h264数据解析成以澳元开头的块,每个块以澳元开头,当另一个开始时结束。 我的理解是,每个解析的“块”(以AUD开头)