590 lines
18 KiB
C++
590 lines
18 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 <iostream>
|
|
#include <cstring>
|
|
#include <memory>
|
|
|
|
#include "WVDRMPluginAPI.h"
|
|
#include "WVStreamControlAPI.h"
|
|
#include "AndroidHooks.h"
|
|
|
|
#define AES_BLOCK_SIZE 16
|
|
|
|
using namespace std;
|
|
|
|
#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
|
|
|
|
/**
|
|
* 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 WVDRMPluginAPI *sDrmPlugin = NULL;
|
|
|
|
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 Terminate()
|
|
{
|
|
WV_Terminate();
|
|
exit(-1);
|
|
}
|
|
|
|
|
|
static void PrintMessage(const char *msg)
|
|
{
|
|
printf("%s", msg);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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);
|
|
|
|
// Create DRM plugin object
|
|
sDrmPlugin = WVDRMPluginAPI::create();
|
|
if (sDrmPlugin == NULL) {
|
|
fprintf(stderr, "FATAL: failed to construct WVMDRMPluginAPI instance\n");
|
|
exit(-1);
|
|
}
|
|
|
|
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]);
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
// enable HTTP logging if you want to debug
|
|
//WV_SetLogging(WV_Logging_HTTP);
|
|
|
|
/*
|
|
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;
|
|
credentials.deviceID="SN12345ABC";
|
|
credentials.streamID="0123456789";
|
|
credentials.portal = "YouTube";
|
|
credentials.clientIP="1.1.1.1";
|
|
credentials.drmServerURL= drmUrl;
|
|
credentials.userData="";
|
|
credentials.drmAckServerURL=DEFAULT_DRM_ACK_URL;
|
|
|
|
string dsPath = "";
|
|
string assetIdStr = "";
|
|
string systemIdStr = "";
|
|
string keyIdStr = "";
|
|
uint_t assetId, systemId, keyId;
|
|
|
|
if (!sDrmPlugin->AcquireDrmInfo(url, credentials, dsPath,
|
|
assetIdStr, systemIdStr, keyIdStr,
|
|
&assetId, &systemId, &keyId)) {
|
|
fprintf(stderr, "ERROR: AcquireDrmInfo failed\n");
|
|
Terminate();
|
|
}
|
|
|
|
if (!sDrmPlugin->ProcessDrmInfo(url)) {
|
|
fprintf(stderr, "ERROR: ProcessDrmInfo failed\n");
|
|
Terminate();
|
|
}
|
|
|
|
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, ¯ovision, &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 %d\n", trickPlayRate);
|
|
break;
|
|
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();
|
|
|
|
if (output)
|
|
fclose(output);
|
|
|
|
set_tty_cooked();
|
|
|
|
return(0);
|
|
}
|