/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mobilyzer.util.video;
import com.mobilyzer.UpdateIntent;
import com.mobilyzer.util.video.player.DemoPlayer;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer.AudioTrackInitializationException;
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
import com.google.android.exoplayer.util.VerboseLogUtil;
import android.content.Intent;
import android.media.MediaCodec.CryptoException;
import android.os.SystemClock;
import android.util.Log;
import android.util.Pair;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
/**
* Logs player events using {@link Log}.
*/
public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener,
DemoPlayer.InternalErrorListener {
public static HashMap<Pair<Integer, Integer>, Integer> Resolution2Bitrate = new HashMap<Pair<Integer, Integer>, Integer>();
public static HashMap<String, Integer> Id2Bitrate = new HashMap<String, Integer>();
private ArrayList<String> dropFrameTime;
private ArrayList<Pair<String, Integer>> videoBitrateVarience;
private ArrayList<Pair<String, Integer>> audioBitrateVarience;
private double initialLoadingTime;
private ArrayList<Double> rebufferTime;
private ArrayList<Pair<String, Double>> videoGoodput;
private ArrayList<Long> videoGoodputEstimate;
private ArrayList<Pair<String, Double>> audioGoodput;
private int previousVideoBitrate;
private int previousAudioBitrate;
private long switchToSteadyStateTime;
private long totalBytesDownloaded;
private double initialLoadingTime_s;
private int bufferCounter = 0;
private double bufferTime_s;
private static final String TAG = "EventLogger";
private static final NumberFormat TIME_FORMAT;
static {
TIME_FORMAT = NumberFormat.getInstance(Locale.US);
TIME_FORMAT.setMinimumFractionDigits(4);
TIME_FORMAT.setMaximumFractionDigits(4);
}
private long sessionStartTimeMs;
private long[] loadStartTimeMs;
public EventLogger() {
loadStartTimeMs = new long[DemoPlayer.RENDERER_COUNT];
this.dropFrameTime = new ArrayList<String>();
this.videoBitrateVarience = new ArrayList<Pair<String, Integer>>();
this.audioBitrateVarience = new ArrayList<Pair<String, Integer>>();
this.rebufferTime = new ArrayList<Double>();
this.videoGoodput = new ArrayList<Pair<String, Double>>();
this.videoGoodputEstimate = new ArrayList<Long>();
this.audioGoodput = new ArrayList<Pair<String, Double>>();
this.videoBitrateVarience.add(Pair.create("0.00", 0));
this.previousVideoBitrate = 0;
this.audioBitrateVarience.add(Pair.create("0.00", 0));
this.previousAudioBitrate = 0;
this.switchToSteadyStateTime = -1;
this.totalBytesDownloaded = 0;
}
public void startSession() {
sessionStartTimeMs = SystemClock.elapsedRealtime();
Log.d(TAG, "start [0]");
}
public Intent endSession() {
Log.d(TAG, "end [" + getSessionTimeString() + "]");
this.videoBitrateVarience.add(Pair.create(getSessionTimeString(), this.previousVideoBitrate));
this.audioBitrateVarience.add(Pair.create(getSessionTimeString(), this.previousAudioBitrate));
return printStatInfo();
}
// DemoPlayer.Listener
@Override
public void onStateChanged(boolean playWhenReady, int state) {
Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", " +
getStateString(state) + "]");
// DecimalFormat df = new DecimalFormat("#.##");
switch(state) {
case ExoPlayer.STATE_PREPARING:
// this.bitrateVarience.add(Pair.create("0.00", currentBitrate));
this.initialLoadingTime_s = Double.parseDouble(getSessionTimeString());
break;
case ExoPlayer.STATE_BUFFERING:
bufferCounter++;
bufferTime_s = Double.parseDouble(getSessionTimeString());
break;
case ExoPlayer.STATE_READY:
if (bufferCounter == 1) {
this.initialLoadingTime = Double.parseDouble(String.format("%.2f", Double.parseDouble(getSessionTimeString()) - this.initialLoadingTime_s));
}
else {
this.rebufferTime.add(Double.parseDouble(String.format("%.2f", Double.parseDouble(getSessionTimeString()) - bufferTime_s)));
}
break;
case ExoPlayer.STATE_ENDED:
this.videoBitrateVarience.add(Pair.create(getSessionTimeString(), this.previousVideoBitrate));
this.audioBitrateVarience.add(Pair.create(getSessionTimeString(), this.previousAudioBitrate));
// printStatInfo();
break;
}
}
private Intent printStatInfo() {
// Log.e("", "DropFrame #: " + this.dropFrameTime.size());
// Log.e("", "Bitrate: " + displayBitrate(this.bitrateVarience));
// Log.e("", "Initial Loading Time: " + this.initialLoadingTime);
// Log.e("", "Rebuffering: " + this.rebufferTime);
Log.e("ashkan_video", "" + this.dropFrameTime.size());
Log.e("ashkan_video", "" + displayGoodPut(this.videoGoodput));
Log.e("ashkan_video", "" + displayBitrate(this.videoBitrateVarience));
Log.e("ashkan_video", "" + this.initialLoadingTime);
Log.e("ashkan_video", "" + this.rebufferTime);
Log.e("ashkan_video", "" + displayGoodPut(this.audioGoodput));
Log.e("ashkan_video", "" + displayBitrate(this.audioBitrateVarience));
Log.e("ashkan_video", "bba switch time " +this.switchToSteadyStateTime );
Log.e("ashkan_video", "total bytes downloaded " +this.totalBytesDownloaded );
Intent videoQoEResult = new Intent();
videoQoEResult.setAction(UpdateIntent.VIDEO_MEASUREMENT_ACTION);
videoQoEResult.putExtra(UpdateIntent.VIDEO_TASK_PAYLOAD_NUM_FRAME_DROPPED, this.dropFrameTime.size());
videoQoEResult.putExtra(UpdateIntent.VIDEO_TASK_PAYLOAD_INITIAL_LOADING_TIME, this.initialLoadingTime);
if(this.switchToSteadyStateTime!=-1){
videoQoEResult.putExtra(UpdateIntent.VIDEO_TASK_PAYLOAD_BBA_SWITCH_TIME, this.switchToSteadyStateTime);
}
if(this.totalBytesDownloaded!=0){
videoQoEResult.putExtra(UpdateIntent.VIDEO_TASK_PAYLOAD_BYTE_USED, this.totalBytesDownloaded);
}
double[] rebufferTimeArray = new double[this.rebufferTime.size()];
int counter = 0;
for (Double rebufferSample : this.rebufferTime) {
rebufferTimeArray[counter] = rebufferSample;
counter++;
}
videoQoEResult.putExtra(UpdateIntent.VIDEO_TASK_PAYLOAD_REBUFFER_TIME, rebufferTimeArray);
String[] goodputTimestamp = new String[this.videoGoodput.size()];
double[] goodputValue = new double[this.videoGoodput.size()];
long[] goodputEstimate = new long[this.videoGoodputEstimate.size()];
counter = 0;
for (Pair<String, Double> goodputSample : this.videoGoodput) {
goodputTimestamp[counter] = goodputSample.first;
goodputValue[counter] = goodputSample.second;
counter++;
}
counter=0;
for (Long estimate : this.videoGoodputEstimate) {
goodputEstimate[counter] = estimate;
counter++;
}
videoQoEResult.putExtra(UpdateIntent.VIDEO_TASK_PAYLOAD_GOODPUT_TIMESTAMP, goodputTimestamp);
videoQoEResult.putExtra(UpdateIntent.VIDEO_TASK_PAYLOAD_GOODPUT_VALUE, goodputValue);
videoQoEResult.putExtra(UpdateIntent.VIDEO_TASK_PAYLOAD_GOODPUT_ESTIMATE_VALUE, goodputEstimate);
String[] bitrateTimestamp = new String[this.videoBitrateVarience.size()];
int[] bitrateValue = new int[this.videoBitrateVarience.size()];
counter=0;
for (Pair<String, Integer> bitrateSample : this.videoBitrateVarience) {
bitrateTimestamp[counter] = bitrateSample.first;
bitrateValue[counter] = bitrateSample.second;
counter++;
}
videoQoEResult.putExtra(UpdateIntent.VIDEO_TASK_PAYLOAD_BITRATE_TIMESTAMP, bitrateTimestamp);
videoQoEResult.putExtra(UpdateIntent.VIDEO_TASK_PAYLOAD_BITRATE_VALUE, bitrateValue);
return videoQoEResult;
}
private String displayBitrate(ArrayList<Pair<String, Integer>> bitrateVarience2) {
StringBuilder sBuilder = new StringBuilder();
for (Pair<String, Integer> pBitrate : bitrateVarience2) {
sBuilder.append(pBitrate.first + " " + pBitrate.second + "\n");
}
return sBuilder.toString();
}
private String displayGoodPut(ArrayList<Pair<String, Double>> goodput) {
StringBuilder sBuilder = new StringBuilder();
for (Pair<String, Double> pBitrate : goodput) {
sBuilder.append(pBitrate.first + " " + String.format("%.2f", pBitrate.second) + "\n");
}
return sBuilder.toString();
}
// Error finished, return partial results
@Override
public void onError(Exception e) {
Log.e(TAG, "playerFailed [" + getSessionTimeString() + "]", e);
// this.videoBitrateVarience.add(Pair.create(getSessionTimeString(), this.previousVideoBitrate));
// this.audioBitrateVarience.add(Pair.create(getSessionTimeString(), this.previousAudioBitrate));
// printStatInfo();
}
@Override
public void onVideoSizeChanged(int width, int height) {
// currentBitrate = Resolution2Bitrate.get(Pair.create(width, height));
Log.d(TAG, "videoSizeChanged [" + getSessionTimeString() + ", " + width + ", " + height + "]");
// this.bitrateVarience.add(Pair.create(getSessionTimeString(), currentBitrate));
}
// DemoPlayer.InfoListener
@Override
// public void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate) {
public void onBandwidthSample(String label, long startTime, long endTime, int elapsedMs, long bytes, long bitrateEstimate) {
Log.d(TAG, label + " bandwidth [" + startTime + ", " + endTime + ", " + getSessionTimeString() + ", " + bytes +
", " + getTimeString(elapsedMs) + ", " + bitrateEstimate + ", " + bytes * 8 / elapsedMs + "kbps]");
if (label.equals("video")) {
this.videoGoodput.add(Pair.create(getSessionTimeString(), (double)bytes * 8000 / elapsedMs));
this.videoGoodputEstimate.add(bitrateEstimate);
}
else if (label.equals("audio")) {
this.audioGoodput.add(Pair.create(getSessionTimeString(), (double)bytes * 8000 / elapsedMs));
}
// this.goodput.add(Pair.create(getSessionTimeString(), (double)bytes * 8000 / elapsedMs));
}
@Override
public void onDroppedFrames(int count, long elapsed) {
Log.d(TAG, "droppedFrames [" + getSessionTimeString() + ", " + count + "]");
this.dropFrameTime.add(getSessionTimeString());
}
@Override
public void onLoadStarted(int sourceId, String formatId, int trigger, boolean isInitialization,
int mediaStartTimeMs, int mediaEndTimeMs, long length) {
loadStartTimeMs[sourceId] = SystemClock.elapsedRealtime();
if (VerboseLogUtil.isTagEnabled(TAG)) {
Log.v(TAG, "loadStart [" + getSessionTimeString() + ", " + sourceId
+ ", " + mediaStartTimeMs + ", " + mediaEndTimeMs + "]");
}
}
@Override
public void onLoadCompleted(int sourceId, long bytesLoaded) {
if (VerboseLogUtil.isTagEnabled(TAG)) {
long downloadTime = SystemClock.elapsedRealtime() - loadStartTimeMs[sourceId];
Log.v(TAG, "loadEnd [" + getSessionTimeString() + ", " + sourceId + ", " +
downloadTime + "]");
}
}
@Override
public void onVideoFormatEnabled(String formatId, int trigger, int mediaTimeMs) {
int currentBitrate = Id2Bitrate.get(formatId);
Log.d(TAG, "videoFormat [" + getSessionTimeString() + ", " + formatId + ", " +
Integer.toString(trigger) + ", " + currentBitrate / 1000 + "kbps" + "]");
this.videoBitrateVarience.add(Pair.create(getSessionTimeString(), this.previousVideoBitrate));
this.videoBitrateVarience.add(Pair.create(getSessionTimeString(), currentBitrate));
this.previousVideoBitrate = currentBitrate;
}
@Override
public void onAudioFormatEnabled(String formatId, int trigger, int mediaTimeMs) {
int currentBitrate = Id2Bitrate.get(formatId);
Log.d(TAG, "audioFormat [" + getSessionTimeString() + ", " + formatId + ", " +
Integer.toString(trigger) + ", " + currentBitrate / 1000 + "kbps" + "]");
this.audioBitrateVarience.add(Pair.create(getSessionTimeString(), this.previousAudioBitrate));
this.audioBitrateVarience.add(Pair.create(getSessionTimeString(), currentBitrate));
this.previousAudioBitrate = currentBitrate;
}
// DemoPlayer.InternalErrorListener
@Override
public void onUpstreamError(int sourceId, IOException e) {
printInternalError("upstreamError", e);
}
@Override
public void onConsumptionError(int sourceId, IOException e) {
printInternalError("consumptionError", e);
}
@Override
public void onRendererInitializationError(Exception e) {
printInternalError("rendererInitError", e);
}
@Override
public void onDrmSessionManagerError(Exception e) {
printInternalError("drmSessionManagerError", e);
}
@Override
public void onDecoderInitializationError(DecoderInitializationException e) {
printInternalError("decoderInitializationError", e);
}
@Override
public void onAudioTrackInitializationError(AudioTrackInitializationException e) {
printInternalError("audioTrackInitializationError", e);
}
@Override
public void onCryptoError(CryptoException e) {
printInternalError("cryptoError", e);
}
private void printInternalError(String type, Exception e) {
Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e);
}
private String getStateString(int state) {
switch (state) {
case ExoPlayer.STATE_BUFFERING:
return "B";
case ExoPlayer.STATE_ENDED:
return "E";
case ExoPlayer.STATE_IDLE:
return "I";
case ExoPlayer.STATE_PREPARING:
return "P";
case ExoPlayer.STATE_READY:
return "R";
default:
return "?";
}
}
private String getSessionTimeString() {
return getTimeString(SystemClock.elapsedRealtime() - sessionStartTimeMs);
// return getTimeString(System.currentTimeMillis());
}
private String getTimeString(long timeMs) {
return TIME_FORMAT.format((timeMs) / 1000f);
}
@Override
public void onSwitchToSteadyState(long elapsedMs) {
this.switchToSteadyStateTime=elapsedMs;
}
@Override
public void onAllChunksDownloaded(long totalBytes) {
this.totalBytesDownloaded=totalBytes;
}
}