如何写精华回答,获更多曝光?
发布
服务器收到解码的流数据后进行编码再播放,当数据量大的时候,长时间播放总是会导致内存问题,板子会自动重启,请问我代码哪里存在问题?
jni层服务器返回的解码后的数据
/**
* 根据资源id存放,jni回调的解码数据
*/
public static HashMap<Integer, LinkedBlockingQueue<FrameData>> decodeMap = new HashMap<>();
//对象池
public static LinkedBlockingQueue<FrameData> frameDataPool = new LinkedBlockingQueue<>(50);
/**
* jni层返回解码后的数据
* @param res 播放资源ID
* @param codecid 解码器类型 (h264=27,h265=173,mpeg4=12,vp8=139,vp9=167)
* @param w 视频的宽
* @param h 视频的高
* @param packet 解码数据
*/
public int callback_videodecode(int isKeyframe, int res, int codecid, int w, int h, byte[] packet, long pts, byte[] codecdata) {
if (packet != null) {
FrameData frameData = frameDataPool.poll();
if (frameData == null) {
//对象池中没有了就新建一个
frameData = new FrameData();
}
frameData.setIsKeyFrame(isKeyframe);
frameData.setRes(res);
frameData.setCodecid(codecid);
frameData.setW(w);
frameData.setH(h);
frameData.setPts(pts);
frameData.setBytes(packet);
frameData.setCodecdata(codecdata);
LinkedBlockingQueue<FrameData> decodeQueue = decodeMap.get(res);
decodeQueue.offer(frameData);
}
return 0;
}
自定义播放的view
public class PlayerView extends SurfaceView implements SurfaceHolder.Callback {
private DecodeThread decodeThread;
private Surface mSurface;
private int resourceId = 0;
private final AtomicBoolean mIsRunning = new AtomicBoolean(true);
public PlayerView(Context context) {
super(context);
init();
}
public PlayerView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public PlayerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
getHolder().addCallback(this);
}
public int getResourceId() {
return resourceId;
}
public void setResourceId(int resourceId) {
this.resourceId = resourceId;
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
mSurface = holder.getSurface();
startDecode();
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
stopDecode();
}
public void startDecode() {
if (decodeThread == null) {
mIsRunning.set(true);
decodeThread = new DecodeThread("pure-" + resourceId);
LogUtils.i("---startDecode---" + decodeThread);
decodeThread.start();
}
}
public void stopDecode() {
if (decodeThread != null) {
LogUtils.i("---stopDecode---" + decodeThread);
mIsRunning.set(false);
decodeThread.interrupt();
decodeThread = null;
}
}
class DecodeThread extends Thread {
private MediaCodec mediaCodec;
private final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
private final LinkedBlockingQueue<FrameData> cacheQueue = new LinkedBlockingQueue<>(25);
private String saveMimeType = "";
private int initW;
private int initH;
public DecodeThread(@NonNull String name) {
super(name);
}
@Override
public void run() {
try {
LinkedBlockingQueue<FrameData> decodeQueue = CallValue.decodeMap.get(resourceId);
byte[] packet;
int packetSize;
int width, height, codecid, isKeyFrame;
long pts;
String mimeType;
FrameData bean;
int cacheQueueSize, decodeQueueSize;
while (mIsRunning.get()) {
cacheQueueSize = cacheQueue.size();
decodeQueueSize = decodeQueue.size();
if (mSurface == null) {
if (decodeQueueSize > 0) {
//回收对象
if (!CallValue.frameDataPool.offer(decodeQueue.poll())) {
LogUtils.i("---recycle object err---");
} else {
LogUtils.i("---recycle object---");
}
}
//跳过循环
continue;
}
if (decodeQueueSize > 0 || cacheQueueSize > 0) {
//先取缓存的数据
bean = cacheQueueSize > 0 ? cacheQueue.peek() : decodeQueue.poll();
packet = bean.getBytes();
packetSize = bean.getPacketSize();
width = bean.getW();
height = bean.getH();
isKeyFrame = bean.getIsKeyFrame();
if (initW != 0 && initW != width) {
LogUtils.i("宽度改变:" + width);
}
if (initH != 0 && initH != height) {
LogUtils.i("高度改变:" + height);
}
pts = bean.getPts();
codecid = bean.getCodecid();
mimeType = getMimeType(codecid);
//已经初始化过了,但是参数有变化的时候需要重新初始化
if (!saveMimeType.equals(mimeType) || initW != width || initH != height || mediaCodec == null) {
//进入初始化MediaCodec
if (mediaCodec != null) {
LogUtils.i("重新配置mediaCodec");
//调用stop方法使其进入 uninitialzed 状态,这样才可以重新配置MediaCodec
mediaCodec.stop();
}
saveMimeType = mimeType;
//初始化MediaCodec
initCodec(width, height, bean.getCodecdata());
}
if (mediaCodec == null) {//初始化失败
if (cacheQueueSize > 0) {//使用的是缓存中的数据
//把它删掉
FrameData poll = cacheQueue.poll();
//回收对象,已经满了再调用offer添加数据会返回false
CallValue.frameDataPool.offer(poll);
} else {//使用的是新数据
//放入缓存队列
if (!cacheQueue.offer(bean)) {
//已经满了,把旧的删除掉
cacheQueue.poll();
//然后将这个新的放入
cacheQueue.offer(bean);
}
}
return;
}
if (mediaCodecDecode(packet, pts, packetSize)) {
if (cacheQueueSize > 0) {
//使用缓存数据,编码成功就丢掉
cacheQueue.poll();
}
//回收对象,已经满了再调用offer添加数据会返回false
if (!CallValue.frameDataPool.offer(bean)) {
LogUtils.i("---recycle object---");
bean = null;
}
} else {//编码失败
if (cacheQueueSize == 0) {//使用的是新数据
LogUtils.i("after encoding fails, put it into the cache queue:" + bean.toString());
//放入缓存队列
if (!cacheQueue.offer(bean)) {
//已经满了,把旧的删除掉
cacheQueue.poll();
//然后将这个新的放入
cacheQueue.offer(bean);
}
}
}
} else {
if (mediaCodec != null) {
//外部没有数据了,还需要渲染已经放入解码器中的数据
mediaCodecDecode(null, 0, 0);
}
}
}
cacheQueue.clear();//清空缓存队列
releaseMediaCodec();
LogUtils.i("thread id=" + Thread.currentThread().getId() + ",end decoding decodeQueue=" + decodeQueue.size());
CallValue.frameDataPool.clear();
decodeQueue.clear();//清空接收队列,避免下次存放时留下脏数据
} catch (Exception e) {
LogUtils.e(e);
e.printStackTrace();
}
}
/**
* 初始化解码器
*
* @param w 宽
* @param h 高
* @param codecdata pps/sps 编码配置数据
*/
private void initCodec(int w, int h, byte[] codecdata) {
try {
LogUtils.i("initCodec : ---start--- ");
//创建了一个解码器,此时编解码器处于未初始化状态(Uninitialized)
mediaCodec = MediaCodec.createDecoderByType(saveMimeType);
//宽高要判断是否是解码器所支持的范围
MediaCodecInfo.CodecCapabilities capabilitiesForType = mediaCodec.getCodecInfo().getCapabilitiesForType(saveMimeType);
MediaCodecInfo.VideoCapabilities videoCapabilities = capabilitiesForType.getVideoCapabilities();
Range<Integer> supportedWidths = videoCapabilities.getSupportedWidths();
Range<Integer> supportedHeights = videoCapabilities.getSupportedHeights();
initW = w;
initH = h;
if (w < h) {
LogUtils.i("宽度比高度小:w=" + w + ",h=" + h);
int temp = w;
w = h;
h = temp;
}
w = supportedWidths.clamp(w);
h = supportedHeights.clamp(h);
LogUtils.d("initCodec 可支持的宽高:" + supportedWidths + "、" + supportedHeights + ",initW=" + initW + ",initH=" + initH);
MediaFormat mediaFormat = MediaFormat.createVideoFormat(saveMimeType, w, h);
mediaFormat.setInteger(MediaFormat.KEY_WIDTH, w);
mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, h);
if (codecdata != null && codecdata.length > 0) {
LogUtils.i("initCodec 设置 csd= " + Arrays.toString(codecdata));
mediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(codecdata));
mediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(codecdata));
}
boolean formatSupported = capabilitiesForType.isFormatSupported(mediaFormat);
LogUtils.i("initCodec : 是否支持 --> " + formatSupported + ",mediaFormat=" + mediaFormat);
//2.对编解码器进行配置,这将使编解码器转为配置状态(Configured)
mediaCodec.configure(mediaFormat, mSurface, null, 0);
LogUtils.i("initCodec : ---configure--- ");
//3.调用start()方法使其转入执行状态(Executing)
mediaCodec.start();
LogUtils.i("initCodec : ---end--- ");
//要保持屏幕纵横比:此方法必须在configure和start之后执行才有效
//VIDEO_SCALING_MODE_SCALE_TO_FIT //默认铺满
//VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING //保持比例,多余会被裁剪
// mediaCodec.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
} catch (Exception e) {
LogUtils.e(e);
e.printStackTrace();
}
}
/**
* 解码数据
*
* @param packet 帧数据包
* @param pts 此缓冲区的呈现时间戳(以微秒为单位)。这通常是应该呈现(渲染)此缓冲区的媒体时间。使用输出表面时,这将作为帧的时间戳传播(转换为纳秒后)
* @param packetSize 帧数据大小
* @return 成功将数据放入解码器则返回true
*/
private boolean mediaCodecDecode(byte[] packet, long pts, int packetSize) {
int inputBufferIndex = -1;
//判断解码器是否初始化完成
if (mediaCodec == null) {
LogUtils.e("mediaCodec not ready");
return false;
}
try {
if (packet != null) {
//队列中有视频帧,检查解码队列中是否有空闲可用的buffer,有则取视频帧送进去解码
inputBufferIndex = mediaCodec.dequeueInputBuffer(0);
if (inputBufferIndex >= 0) {
//有空闲可用的解码buffer
ByteBuffer byteBuffer = mediaCodec.getInputBuffer(inputBufferIndex);
byteBuffer.clear();
byteBuffer.limit(packetSize);
byteBuffer.position(0);
//将视频队列中的头取出送到解码队列中
byteBuffer.put(packet, 0, packetSize);
mediaCodec.queueInputBuffer(inputBufferIndex, 0, packetSize, pts, 0);
}
}
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(info, 0);
if (outputBufferIndex >= 0) {
//如果配置编码器时指定了有效的surface,传true将此输出缓冲区显示在surface
mediaCodec.releaseOutputBuffer(outputBufferIndex, true);
}
} catch (Exception e) {
e.printStackTrace();
LogUtils.e(e);
}
return inputBufferIndex >= 0;
}
/**
* 释放资源
*/
private void releaseMediaCodec() {
if (mediaCodec != null) {
try {
LogUtils.e("---releaseMediaCodec---");
//使用完编解码器后,你必须调用release()方法释放其资源
mediaCodec.release();
} catch (Exception e) {
LogUtils.e(e);
e.printStackTrace();
}
mediaCodec = null;
}
}
/**
* 获取指定的MimeType
*
* @param codecId 后台回调ID
* @return MimeType
*/
private String getMimeType(int codecId) {
switch (codecId) {
case 12:
case 13:
return "video/mp4v-es";
case 139:
case 140:
return "video/x-vnd.on2.vp8";
case 167:
case 168:
return "video/x-vnd.on2.vp9";
case 173:
case 174:
return "video/hevc";
default:
return "video/avc";
}
}
}
}