Recently, I was researching google's ExoPlayer. According to the project requirements, it is necessary to obtain the display time of the current frame. Looking at the source code, I found that decoding is performed in the MediaCodecVideoRenderer class. I found that there is time data in the processOutputBuffer function. According to the test, I know that presentationTimeUs is the display time, and then I want to way to give us time
Since you want to change the source code, first download the source code, the
source code
Create a new module and copy the code and resources in the
core, dash, hls, smoothstreaming, and ui
folders to this module
Create a new interface VideoTimeListener
public interface VideoTimeListener { void onVideoTimeChanged(long time); }
Modify MediaCodecVideoRenderer, add a VideoTimeListener and constructor
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(); }
Added inside renderOutputBuffer and 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