Files
android/proprietary/streamcontrol/test/TestPlayer.cpp
Edwin Wong 1fee8fd75a Install proprietary userspace modules under /vendor.
BUG: 5284436

Change-Id: I9921b8446626b7041a3fc66bff918d3adac5bbe1
2011-10-14 14:09:12 -07:00

658 lines
20 KiB
C++

/*
* Copyright (C) 2011 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.
*/
#include <stdlib.h>
#include <unistd.h>
#include <termio.h>
#include <stdio.h>
#include <stdint.h>
#include <sys/time.h>
#include <dlfcn.h>
#include <iostream>
#include <cstring>
#include <memory>
#include "WVStreamControlAPI.h"
#include "AndroidHooks.h"
#include "WVMDrmPlugin.h"
#include "drm/DrmInfoRequest.h"
#include "drm/DrmInfoStatus.h"
#include "drm/DrmConstraints.h"
#include "drm/DrmInfo.h"
#define AES_BLOCK_SIZE 16
using namespace std;
using namespace android;
#define DEFAULT_BLOCK_SIZE 16*1024
#define DEFAULT_PLAYBACK_BUFFER_SIZE 0*1024*1024
#define DEFAULT_START_TIME "now"
#define DEFAULT_DRM_URL "http://wstfcps005.shibboleth.tv/widevine/cypherpc/cgi-bin/GetEMMs.cgi"
#define DEFAULT_DRM_ACK_URL "http://wstfcps005.shibboleth.tv/widevine/cypherpc/cgi-bin/Ack.cgi"
#define SHOW_BITRATE 1
static void Terminate();
/**
* Print command line options
*/
void PrintUsage(char *prog)
{
printf("Usage: %s <options> url\n", prog);
printf(" -o output_file\n");
printf(" -b block_size (default: %d)\n", DEFAULT_BLOCK_SIZE);
printf(" -p playback_buffer_size (default: %d)\n", (int)DEFAULT_PLAYBACK_BUFFER_SIZE);
printf(" -m print PTS -> media time\n");
printf(" -s start_time (default: %s)\n", DEFAULT_START_TIME);
printf(" -d drm_url\n");
exit(-1);
}
static IDrmEngine *sDrmPlugin = NULL;
static void *sSharedLibHandle = NULL;
static bool sWVInitialized = false;
static struct termios termattr, save_termattr;
static int ttysavefd = -1;
static enum
{
RESET, RAW, CBREAK
} ttystate = RESET;
/**
***************************************************************************
*
* set_tty_raw(), put the user's TTY in one-character-at-a-time mode.
*
* @returns 0 on success, -1 on failure.
*
*************************************************************************** */
int set_tty_raw(void)
{
int i;
i = tcgetattr(STDIN_FILENO, &termattr);
if (i < 0)
{
printf("tcgetattr() returned %d for fildes=%d\n",i,STDIN_FILENO);
perror("");
return -1;
}
save_termattr = termattr;
termattr.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
termattr.c_iflag &= ~(ICRNL | INPCK | ISTRIP | IXON); /* | BRKINT */
termattr.c_cflag &= ~(CSIZE | PARENB);
termattr.c_cflag |= CS8;
/* termattr.c_oflag &= ~(OPOST); */
termattr.c_cc[VMIN] = 0; /* or 0 for some Unices; see note 1 */
termattr.c_cc[VTIME] = 0;
i = tcsetattr(STDIN_FILENO, TCSANOW, &termattr);
if (i < 0)
{
printf("tcsetattr() returned %d for fildes=%d\n",i,STDIN_FILENO);
perror("");
return -1;
}
ttystate = RAW;
ttysavefd = STDIN_FILENO;
return 0;
}
/**
* @return time of day in milliseconds
*/
static uint64_t get_time_in_ms()
{
uint64_t ms;
timeval t;
gettimeofday(&t, NULL);
ms = (uint64_t)(t.tv_sec) * 1000;
ms +=(uint64_t)(t.tv_usec) / 1000;
return ms;
}
/**
***************************************************************************
*
* set_tty_cooked(), restore normal TTY mode. Very important to call
* the function before exiting else the TTY won't be too usable.
*
* @returns 0 on success, -1 on failure.
*
*************************************************************************** */
int set_tty_cooked(void)
{
int i;
if (ttystate != CBREAK && ttystate != RAW)
{
return 0;
}
i = tcsetattr(STDIN_FILENO, TCSAFLUSH, &save_termattr);
if (i < 0)
{
return -1;
}
ttystate = RESET;
return 0;
}
/**
***************************************************************************
*
* kb_getc(), if there's a typed character waiting to be read,
*
* @return character; else return 0.
*
*************************************************************************** */
unsigned char kb_getc(void)
{
unsigned char ch;
ssize_t size;
size = read(STDIN_FILENO, &ch, 1);
if (size == 0)
{
return 0;
}
else
{
return ch;
}
}
static void PrintMessage(const char *msg)
{
printf("%s", msg);
}
static void OpenDrmPlugin()
{
const char *path = "/vendor/lib/drm/libdrmwvmplugin.so";
sSharedLibHandle = dlopen(path, RTLD_NOW);
if (sSharedLibHandle == NULL) {
fprintf(stderr, "Can't open plugin: %s\n", path);
Terminate();
}
typedef IDrmEngine *(*create_t)();
create_t creator = (create_t)dlsym(sSharedLibHandle, "create");
if (!creator) {
fprintf(stderr, "Can't find create method\n");
Terminate();
}
sDrmPlugin = (*creator)();
if (sDrmPlugin->initialize(0) != DRM_NO_ERROR) {
fprintf(stderr, "onInitialize failed!\n");
Terminate();
}
}
static void CloseDrmPlugin()
{
if (sSharedLibHandle) {
if (sDrmPlugin) {
typedef IDrmEngine *(*destroy_t)(IDrmEngine *plugin);
destroy_t destroyer = (destroy_t)dlsym(sSharedLibHandle, "destroy");
if (destroyer) {
(*destroyer(sDrmPlugin));
sDrmPlugin = NULL;
} else
fprintf(stderr, "Can't find destroy method\n");
}
dlclose(sSharedLibHandle);
sSharedLibHandle = NULL;
}
}
static void AcquireRights(IDrmEngine *plugin, string url, string drmUrl)
{
String8 mimeType("video/wvm");
DrmInfoRequest rightsAcquisitionInfo(DrmInfoRequest::TYPE_RIGHTS_ACQUISITION_INFO, mimeType);
rightsAcquisitionInfo.put(String8("WVDRMServerKey"), String8(drmUrl.c_str()));
rightsAcquisitionInfo.put(String8("WVAssetURIKey"), String8(url.c_str()));
rightsAcquisitionInfo.put(String8("WVDeviceIDKey"), String8("device1234"));
rightsAcquisitionInfo.put(String8("WVPortalKey"), String8("YouTube"));
rightsAcquisitionInfo.put(String8("WVLicenseTypeKey"), String8("1"));
DrmInfo *info = plugin->acquireDrmInfo(0, &rightsAcquisitionInfo);
if (info == NULL) {
fprintf(stderr, "acquireDrmInfo failed!\n");
Terminate();
}
DrmInfoStatus *status = plugin->processDrmInfo(0, info);
if (status == NULL || status->statusCode != DrmInfoStatus::STATUS_OK) {
fprintf(stderr, "processDrmInfo failed!\n");
Terminate();
}
if (plugin->checkRightsStatus(0, String8(url.c_str()), Action::DEFAULT) != RightsStatus::RIGHTS_VALID) {
fprintf(stderr, "checkValidRights default action failed!\n");
Terminate();
}
delete status;
delete info;
}
static void Terminate()
{
if (sWVInitialized)
WV_Terminate();
CloseDrmPlugin();
exit(-1);
}
static void _cb1(char *a, unsigned long b)
{
DrmBuffer buf(a, b);
sDrmPlugin->initializeDecryptUnit(0, NULL, 0, &buf);
}
/**
* Program entry pointer
*
* @return 0 for success, -1 for error
*/
int main( int argc, char *argv[] )
{
int option;
string url, outputFile, startTime = DEFAULT_START_TIME;
unsigned long blockSize = DEFAULT_BLOCK_SIZE;
unsigned long playbackBufferSize = DEFAULT_PLAYBACK_BUFFER_SIZE;
bool ptsToMediaTime = false;
string drmUrl = DEFAULT_DRM_URL;
_ah006(PrintMessage);
while ((option = getopt(argc, argv, "o:b:p:s:mD:d:")) != -1) {
switch (option) {
case 'o':
outputFile = optarg;
break;
case 'b':
if (sscanf(optarg, "%lu", &blockSize) != 1)
PrintUsage(argv[0]);
break;
case 'p':
if (sscanf(optarg, "%lu", &playbackBufferSize) != 1)
PrintUsage(argv[0]);
break;
case 's':
startTime = optarg;
break;
case 'm':
ptsToMediaTime = true;
break;
case 'd':
drmUrl = optarg;
break;
default:
printf("unknown option: '%c'\n", option);
PrintUsage(argv[0]);
}
}
if ((argc - optind) != 1)
PrintUsage(argv[0]);
url = argv[optind];
FILE *output = NULL;
if (outputFile.size()) {
output = fopen(outputFile.c_str(), "wb");
if (!output) {
fprintf(stderr, "unable to open output file %s for writing\n", argv[2]);
Terminate();
}
}
// This turns off some verbose printing
setenv("WV_SILENT", "true", 1);
WVStatus status = WV_Initialize( NULL );
if (status != WV_Status_OK) {
fprintf(stderr, "ERROR: WV_Initialize returned status %d\n", (int)status);
Terminate();
} else
sWVInitialized = true;
// enable HTTP logging if you want to debug
WV_SetLogging(WV_Logging_HTTP);
OpenDrmPlugin();
AcquireRights(sDrmPlugin, url, drmUrl);
_ah002(_cb1);
/*
status = WV_StartBandwidthCheck( url.c_str() );
if (status != WV_Status_OK) {
fprintf(stderr, "ERROR: WV_CheckBandwidth returned status %d\n", (int)status);
Terminate();
}
unsigned long bandwidth;
do {
usleep(100000);
// The idea here is the bandwidth check is done in the background while the GUI/OSD is
// doing other things. In this example, we just wait for the result.
status = WV_GetBandwidthCheckStatus(&bandwidth);
} while (status == WV_Status_Checking_Bandwidth);
if (status == WV_Status_OK)
cout << "Bandwidth check " << bandwidth << endl;
else
cout << "Bandwidth check failed: " << status << endl;
*/
WVSession *session = 0;
WVCredentials credentials;
status = WV_Setup( session, url.c_str(), "RAW/RAW/RAW;destination=getdata", credentials);
if (status != WV_Status_OK) {
fprintf(stderr, "ERROR: WV_Setup returned status %d\n", (int)status);
if (status == 408)
fprintf(stderr, "TIMEOUT: Make sure your device is powered on and has a network connection\n");
else if (status == 404)
fprintf(stderr, "ASSET NOT FOUND: Make sure the URL you provided is correct\n");
Terminate();
}
WVMacrovision macrovision;
bool hdcp, cit;
status = WV_Info_GetCopyProtection(session, &macrovision, &hdcp, &cit);
switch (status) {
case WV_Status_OK:
printf("Copy protection: macrovison = %d, hdcp = %d, cit = %d\n", (int)macrovision, (int)hdcp, (int)cit);
break;
case WV_Status_Warning_Not_Available:
printf("Warning: Copy protection info not yet available\n");
status = WV_Status_OK;
break;
default:
fprintf(stderr, "ERROR: WV_Info_GetCopyProtection returned status %d\n", (int)status);
Terminate();
}
// Get audio and video config options
WVAudioType audioType;
unsigned short streamId;
unsigned short profile;
unsigned short numChannels;
unsigned long sampleFrequency;
unsigned long bitRate;
WVVideoType videoType;
unsigned short level;
unsigned short width;
unsigned short height;
float pixelAspectRatio;
float frameRate;
WV_Info_GetAudioConfiguration(session, &audioType, &streamId, &profile, &numChannels, &sampleFrequency, &bitRate);
printf("Audio type: %hu\n", audioType);
printf("Audio stream ID: %hu\n", streamId);
printf("Audio profile: %hu\n", profile);
printf("Audio channels: %hu\n", numChannels);
printf("Audio sampling freq: %lu\n", sampleFrequency);
printf("Audio bit rate: %lu\n", bitRate);
WV_Info_GetVideoConfiguration(session, &videoType, &streamId, &profile, &level, &width, &height, & pixelAspectRatio, &frameRate, &bitRate);
printf("Video type: %hu\n", videoType);
printf("Video stream ID: %hu\n", streamId);
printf("Video profile: %hu\n", profile);
printf("Video profile level: %hu\n", level);
printf("Video width: %hu\n", width);
printf("Video height: %hu\n", height);
printf("Video pixel aspect ratio: %f\n", pixelAspectRatio);
printf("Video frame rate: %f\n", frameRate);
printf("Video bit rate: %lu\n", bitRate);
float scale_used;
startTime += "-";
status = WV_Play( session, 1.0, &scale_used, startTime.c_str() );
if (status != WV_Status_OK) {
fprintf(stderr, "ERROR: WV_Play returned status %d\n", (int)status);
Terminate();
}
auto_ptr<uint8_t> buffer(new uint8_t[blockSize]);
size_t numBytes;
// fill playback buffer as quickly as possible
uint64_t bytesRead = 0;
while (bytesRead < playbackBufferSize) {
status = WV_GetData( session, buffer.get(), blockSize, &numBytes, 0 );
switch (status) {
case WV_Status_OK:
break;
case WV_Status_Warning_Download_Stalled:
case WV_Status_Warning_Need_Key:
fprintf(stderr, "WARNING: WV_GetData returned status %d\n", (int)status);
usleep(100000);
break;
default:
fprintf(stderr, "ERROR: WV_GetData returned status %d\n", (int)status);
Terminate();
break;
}
if (numBytes > 0) {
if (output)
fwrite(buffer.get(), numBytes, 1, output);
bytesRead += numBytes;
cout << "Read " << numBytes << "/" << bytesRead << " out of " << playbackBufferSize << endl;
}
}
set_tty_raw();
#if SHOW_BITRATE
unsigned long bitRates[32];
size_t numBitRates;
size_t curBitRate;
if (WV_Info_GetAdaptiveBitrates(session, bitRates, sizeof(bitRates)/sizeof(uint32_t),
&numBitRates, &curBitRate) == WV_Status_OK) {
printf("Bit Rates: ");
for (uint32_t idx = 0; idx < numBitRates; ++idx) {
if (idx == curBitRate)
printf("*%lu* ", bitRates[idx]);
else
printf("%lu ", bitRates[idx]);
}
printf("\n");
}
#endif
string nptTime = WV_Info_GetTime(session);
int hh, mm;
float ss;
sscanf(nptTime.c_str(), "%d:%d:%f", &hh, &mm, &ss);
uint64_t startMs = (uint64_t)((hh * 3600000) + (mm * 60000) + (ss * 1000));
uint64_t curMs = startMs;
uint64_t lastMs = curMs;
uint64_t baseTime = get_time_in_ms();
int trickPlayRate = 1;
bool quit = false;
while (!quit) {
uint64_t curTime = get_time_in_ms();
uint64_t streamTimeRef = (trickPlayRate >= 0) ? (curMs - startMs) : (startMs - curMs);
uint64_t clockRef = get_time_in_ms() - baseTime;
if (trickPlayRate)
clockRef *= trickPlayRate > 0 ? trickPlayRate : -trickPlayRate;
if (clockRef > streamTimeRef) {
// time for another pull
status = WV_GetData( session, buffer.get(), blockSize, &numBytes, 0 );
switch (status) {
case WV_Status_OK:
break;
case WV_Status_End_Of_Media:
printf("End of Media\n");
if (trickPlayRate < 0) {
WV_Play( session, 1.0, &scale_used, "00:00:00-" );
trickPlayRate = 1;
startMs = curMs;
baseTime = curTime;
} else
quit = true;
break;
case 1001:
fprintf(stderr, "ERROR: WV_GetData returned status %d\n", (int)status);
break;
case WV_Status_Warning_Download_Stalled:
case WV_Status_Warning_Need_Key:
fprintf(stderr, "WARNING: WV_GetData returned status %d\n", (int)status);
usleep(100000);
break;
default:
fprintf(stderr, "ERROR: WV_GetData returned status %d\n", (int)status);
Terminate();
break;
}
if (numBytes > 0) {
if (output)
fwrite(buffer.get(), numBytes, 1, output);
bytesRead += numBytes;
nptTime = WV_Info_GetTime(session);
sscanf(nptTime.c_str(), "%d:%d:%f", &hh, &mm, &ss);
curMs = (uint64_t)((hh * 3600000) + (mm * 60000) + (ss * 1000));
if (curMs != lastMs) {
int64_t msDif = (trickPlayRate >= 0) ? (curMs - lastMs) : (lastMs - curMs);
if ((msDif < 0) || (msDif > 60000)) {
// discontinuity
startMs = curMs;
baseTime = curTime;
printf("Current time (skip): %s\n", nptTime.c_str());
} else if ((curMs / 1000) != (lastMs / 1000)) {
printf("Current time: %s\n", nptTime.c_str());
#if SHOW_BITRATE
if (WV_Info_GetAdaptiveBitrates(session, bitRates, sizeof(bitRates)/sizeof(uint32_t),
&numBitRates, &curBitRate) == WV_Status_OK) {
printf("Bit Rates: ");
for (uint32_t idx = 0; idx < numBitRates; ++idx) {
if (idx == curBitRate)
printf("*%lu* ", bitRates[idx]);
else
printf("%lu ", bitRates[idx]);
}
printf("\n");
}
#endif
}
lastMs = curMs;
}
}
}
unsigned char kbhit = kb_getc();
switch (kbhit) {
case 'g':
char seekTime[256];
set_tty_cooked();
printf("Go to time: ");
if ((scanf("%s", seekTime) == 1) && (sscanf(seekTime, "%d:%d:%f", &hh, &mm, &ss) == 3)) {
status = WV_Play(session, 1, &scale_used, string(seekTime) + "-");
if (status != WV_Status_OK) {
fprintf(stderr, "ERROR: WV_Play returned status %d\n", (int)status);
Terminate();
}
startMs = curMs;
baseTime = curTime;
}
set_tty_raw();
break;
case 't':
set_tty_cooked();
printf("Trick-play rate (now): ");
if (scanf("%d", &trickPlayRate) == 1) {
printf( "Got a trick play value of %d\n", trickPlayRate );
if (trickPlayRate == 0)
trickPlayRate = 1;
status = WV_Play(session, trickPlayRate, &scale_used, "now-");
if (status != WV_Status_OK) {
fprintf(stderr, "ERROR: WV_Play returned status %d\n", (int)status);
Terminate();
}
startMs = curMs;
baseTime = curTime;
} else {
printf( "did not get a rate\n" );
}
set_tty_raw();
break;
case 'p':
set_tty_cooked();
printf("PTS: ");
uint64_t pts;
if (scanf("%llu", (long long unsigned int*)& pts) == 1) {
string mediaTime = WV_TimestampToMediaTime(session, pts, PTS);
printf("Media Time: \"%s\"\n", mediaTime.c_str());
}
set_tty_raw();
break;
case 'x':
quit = true;
break;
default:
break;
}
usleep(1000);
};
WV_Teardown( session );
WV_Terminate();
CloseDrmPlugin();
if (output)
fclose(output);
set_tty_cooked();
return(0);
}