因为在工作中,接触到了视频相关的开发工作;同时,大多数android处理音视频多半都是有C++工程师提供处理库,所以,在这里记录一下我自己在工作中遇到的问题。
主要功能:
采集Android摄像头数据,实时编码H264,发送至指定地址(RTP/RTSP/RTMP等等,还有很多其他封装格式,可自行研究)。
本文使用了javacv来处理音视频数据,javacv是一套java封装的jni库,可以适用于本地多媒体(音视频)调用以及音视频,图片等文件后期操作(图片修改,音视频解码剪辑等等功能)。
javacv官方github维护地址:/bytedeco/javacv
实现步骤:
- Android视频采集
- 视频编码为H264
- 建立管道流
PipedInputStream与PipedOutputStream建立连接,将PipedInputStream作为视频源,传给javacv。
PipedInputStream的大小默认为1024,大小视情况来定。(javacv内部读取视频数据是4096)
/**
* 开启编码与javacv录制的通道
*/
private Runnable sendRunnable = new Runnable() {
@Override
public void run() {
//管道流,编码数据与javacv录制建立
PipedInputStream pipedInputStream = null;
try {
pipedInputStream = new PipedInputStream(1024 * 4);
pipedOutputStream = new PipedOutputStream(pipedInputStream);
} catch (IOException e) {
();
}
String mTag;
if (m_CameraId == .CAMERA_FACING_FRONT) {
mTag = "front";
} else {
mTag = "back";
}
javaCvHelper = new JavaCvHelper(pipedInputStream, m_outPath, , mTag);
}
};
- 在编码输出位置,将H264数据写入管道流
在这个地方需要注意一下。javacv会默认发送第一帧信息帧(SPS+PPS),需要在这里将信息帧保留,并不发送第一帧,在后续的I帧前,发送信息帧(方便播放端快速解码显示)。
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = (outputBufferIndex);
byte[] outData = new byte[];
(outData);
//存储SPS/PPS
if ( == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
configbyte = outData;
}
//是关键帧,发送sps\pps
if ( == MediaCodec.BUFFER_FLAG_KEY_FRAME) {
(TAG, "KEY_FRAME !");
mCount = 0;
if (!isFirstFrame) {
sendData(configbyte);
}
}
mCount++;
// (TAG, "_FRAME " + mCount);
if (!isFirstFrame) {
sendData(outData);
}
isFirstFrame = false;
(outputBufferIndex, false);
outputBufferIndex = (bufferInfo, 12000);
}
/**
* 发送数据至javacv
*
* @param outData
*/
private void sendData(final byte[] outData) {
(new Runnable() {
@Override
public void run() {
if (pipedOutputStream != null) {
try {
(outData);
} catch (IOException e) {
();
}
}
}
});
}
- javacv的处理
FFmpegFrameGrabberPlus是重写的FFmpegFrameGrabber;
修改了当视频源为InputStream是,注释了inputStream = new BufferedInputStream(inputStream);因为在测试过程中发现,BufferedInputStream会占用很多的缓存,导致内存暴涨。注释掉,就没什么问题了。
FFmpegFrameRecorderPlus是重写的FFmpegFrameRecorder;
增加了在recordPacket时,pts的同步计算问题。持续使用时间达2小时左右,可能出现视频播放延时60s,经分析,是pts和dts导致的。
如果是rtp地址, (“rtp”)即可;
如果是rtmp地址, (“flv”)即可;
如果是rtsp地址, (“rtsp”)即可;
…
/**
* 数据封装与发送
*
* @throws
* @throws
*/
private void recordStream() throws , {
= true;
//数据采集
FFmpegFrameGrabberPlus grabber = new FFmpegFrameGrabberPlus(inStream);
("vcodec", "copy");
();
(TAG, "视频宽" + ());
(TAG, "视频高" + ());
(TAG, "视频帧率:" + ());
(TAG, "视频比特率:" + ());
//数据录制(推送)
FFmpegFrameRecorderPlus recorder = new FFmpegFrameRecorderPlus(outUrl, (), (), 0);
("rtp");
("preset", "ultrafast");
("tune", "zerolatency");
(());
AVPacket avPacket = null;
int error_times = 0;
while (isRunning && error_times < 1) {
avPacket = ();
if (avPacket == null) {
(TAG, "- Null AVPacket -");
try {
(100);
} catch (InterruptedException e) {
();
}
continue;
}
if (() == avcodec.AV_PKT_FLAG_KEY) {
(TAG, "- AVPacket -");
}
pts = () / 1000;
(pts);
(pts);
error_times += ((avPacket) ? 0 : 1);
avcodec.av_free_packet(avPacket);
avcodec.av_packet_unref(avPacket);
avutil.av_freep(avPacket);
}
();
();
(mTag);
(TAG, "- Stop AVPacket -");
}
- 关于javacv内存释放的问题
avcodec.av_free_packet(avPacket);avcodec.av_packet_unref(avPacket);
avutil.av_freep(avPacket);。
因为有些时候只调用avcodec.av_free_packet(avPacket);是起作用的。都调用,也没什么影响。可以自己选择。
以上是我在工作中碰到的一些问题和解决方案,记录一下。