最近在研究google的ExoPlayer,根据项目需求,需要获得当前帧的显示时间,看源码发现解码在MediaCodecVideoRenderer这个类中执行解码,发现processOutputBuffer函数内有时间数据,根据测试知道presentationTimeUs是显示时间,然后就是想办法把时间给我们了
既然想改源码,那先下载源码,
源码
新建个module,把
core,dash,hls,smoothstreaming,ui文件夹内的代码和资源都复制到这个module内
新建个interface VideoTimeListener
public interface VideoTimeListener { void onVideoTimeChanged(long time); }
修改MediaCodecVideoRenderer,添加一个VideoTimeListener和构造函数
private VideoTimeListener timeListener; public MediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector, long allowedJoiningTimeMs, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys, @Nullable Handler eventHandler, @Nullable VideoRendererEventListener eventListener,VideoTimeListener timeListener, int maxDroppedFramesToNotify) { super(C.TRACK_TYPE_VIDEO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys); this.allowedJoiningTimeMs = allowedJoiningTimeMs; this.maxDroppedFramesToNotify = maxDroppedFramesToNotify; this.context = context.getApplicationContext(); frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(context); eventDispatcher = new EventDispatcher(eventHandler, eventListener); deviceNeedsAutoFrcWorkaround = deviceNeedsAutoFrcWorkaround(); pendingOutputStreamOffsetsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT]; outputStreamOffsetUs = C.TIME_UNSET; joiningDeadlineMs = C.TIME_UNSET; currentWidth = Format.NO_VALUE; currentHeight = Format.NO_VALUE; currentPixelWidthHeightRatio = Format.NO_VALUE; pendingPixelWidthHeightRatio = Format.NO_VALUE; scalingMode = C.VIDEO_SCALING_MODE_DEFAULT; this.timeListener = timeListener; clearReportedVideoSize(); }
在renderOutputBuffer和renderOutputBufferV21内添加
protected void renderOutputBuffer(MediaCodec codec, int index, long presentationTimeUs) { //...省略代码 if(timeListener != null){ timeListener.onVideoTimeChanged(presentationTimeUs/1000); } } @TargetApi(21) protected void renderOutputBufferV21( MediaCodec codec, int index, long presentationTimeUs, long releaseTimeNs) { //...省略代码 if(timeListener != null){ timeListener.onVideoTimeChanged(presentationTimeUs/1000); } }
修改RenderersFactory的createRenderers
Renderer[] createRenderers(Handler eventHandler, VideoRendererEventListener videoRendererEventListener, AudioRendererEventListener audioRendererEventListener, VideoTimeListener videoTimeListener,TextOutput textRendererOutput, MetadataOutput metadataRendererOutput);
修改DefaultRenderersFactory的createRenderers和buildVideoRenderers
@Override public Renderer[] createRenderers(Handler eventHandler, VideoRendererEventListener videoRendererEventListener, AudioRendererEventListener audioRendererEventListener, VideoTimeListener videoTimeListener, TextOutput textRendererOutput, MetadataOutput metadataRendererOutput) { ArrayList<Renderer> renderersList = new ArrayList<>(); buildVideoRenderers(context, drmSessionManager, allowedVideoJoiningTimeMs, eventHandler, videoRendererEventListener,videoTimeListener, extensionRendererMode, renderersList); //省略代码 return renderersList.toArray(new Renderer[renderersList.size()]); } protected void buildVideoRenderers(Context context, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, long allowedVideoJoiningTimeMs, Handler eventHandler, VideoRendererEventListener eventListener,VideoTimeListener videoTimeListener, @ExtensionRendererMode int extensionRendererMode, ArrayList<Renderer> out) { out.add(new MediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT, allowedVideoJoiningTimeMs, drmSessionManager, false, eventHandler, eventListener,videoTimeListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); //省略代码 }
修改SimpleExoPlayer的构造函数
private final CopyOnWriteArraySet<VideoTimeListener> videoTimeListeners; protected SimpleExoPlayer( RenderersFactory renderersFactory, TrackSelector trackSelector, LoadControl loadControl, Clock clock) { componentListener = new ComponentListener(); videoListeners = new CopyOnWriteArraySet<>(); textOutputs = new CopyOnWriteArraySet<>(); metadataOutputs = new CopyOnWriteArraySet<>(); videoDebugListeners = new CopyOnWriteArraySet<>(); audioDebugListeners = new CopyOnWriteArraySet<>(); videoTimeListeners = new CopyOnWriteArraySet<>(); Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper(); Handler eventHandler = new Handler(eventLooper); renderers = renderersFactory.createRenderers(eventHandler, componentListener, componentListener, componentListener, componentListener,componentListener); //省略代码 }
在SimpleExoPlayer添加函数
public void addVideoTiemListener(VideoTimeListener listener){ videoTimeListeners.add(listener); } public void removeVideoTiemListener(VideoTimeListener listener){ videoTimeListeners.remove(listener); }
修改ComponentListener
ComponentListener implements VideoRendererEventListener, AudioRendererEventListener, TextOutput, MetadataOutput, SurfaceHolder.Callback, TextureView.SurfaceTextureListener,VideoTimeListener
@Override public void onVideoTimeChanged(long time) { for (VideoTimeListener videoTimeListener : videoTimeListeners) { videoTimeListener.onVideoTimeChanged(time); } }
好了,准备工作完成,在exoplayer初始化的时候
player.addVideoTiemListener(new VideoTimeListener() { @Override public void onVideoTimeChanged(long time) { videoTime = time; } });
经过和ffmpeg解码出来的数据对比
exoplayer数据
ffmpeg
例子代码
密码:addl