|
|
/**
|
|
|
* The MIT License (MIT)
|
|
|
*
|
|
|
* Copyright (c) 2013-2018 Winlin
|
|
|
*
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
|
* this software and associated documentation files (the "Software"), to deal in
|
|
|
* the Software without restriction, including without limitation the rights to
|
|
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
|
|
* subject to the following conditions:
|
|
|
*
|
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
|
* copies or substantial portions of the Software.
|
|
|
*
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
*/
|
|
|
|
|
|
#include <stdio.h>
|
|
|
#include <stdlib.h>
|
|
|
#include <unistd.h>
|
|
|
#include <getopt.h>
|
|
|
#include <assert.h>
|
|
|
|
|
|
#include "../../objs/include/srs_librtmp.h"
|
|
|
|
|
|
void parse_amf0_object(char* p, srs_amf0_t args)
|
|
|
{
|
|
|
char opvt = 0; // object property value type.
|
|
|
const char* opnp = NULL; // object property name ptr.
|
|
|
const char* opvp = NULL; // object property value ptr.
|
|
|
|
|
|
while (*p) {
|
|
|
switch (*p++) {
|
|
|
case 'O':
|
|
|
while (*p && *p++ != ':') {
|
|
|
}
|
|
|
if (*p++ == '1') {
|
|
|
printf("amf0 object start\n");
|
|
|
} else {
|
|
|
printf("amf0 object end\n");
|
|
|
}
|
|
|
break;
|
|
|
case 'N':
|
|
|
opvt = *p++;
|
|
|
if (*p++ != ':') {
|
|
|
printf("object property must split by :.\n");
|
|
|
exit(-1);
|
|
|
}
|
|
|
opnp = p++;
|
|
|
while (*p && *p++ != ':') {
|
|
|
}
|
|
|
p[-1] = 0;
|
|
|
opvp = p;
|
|
|
printf("amf0 %c property[%s]=%s\n", opvt, opnp, opvp);
|
|
|
switch(opvt) {
|
|
|
case 'S':
|
|
|
srs_amf0_object_property_set(args, opnp, srs_amf0_create_string(opvp));
|
|
|
break;
|
|
|
default:
|
|
|
printf("unsupported object property.\n");
|
|
|
exit(-1);
|
|
|
}
|
|
|
*p=0;
|
|
|
break;
|
|
|
default:
|
|
|
printf("only supports an object arg.\n");
|
|
|
exit(-1);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// srs debug info.
|
|
|
char* ip = NULL;
|
|
|
char* sig = NULL;
|
|
|
int pid = 0, cid = 0;
|
|
|
int major = 0, minor = 0, revision= 0, build = 0;
|
|
|
// User options.
|
|
|
int complex_handshake = 0;
|
|
|
const char* rtmp_url = NULL;
|
|
|
const char* output_flv = NULL;
|
|
|
const char* swfUrl = NULL;
|
|
|
const char* tcUrl = NULL;
|
|
|
const char* pageUrl = NULL;
|
|
|
srs_amf0_t args = NULL;
|
|
|
|
|
|
int do_proxy(srs_rtmp_t rtmp, srs_flv_t flv)
|
|
|
{
|
|
|
int ret = 0;
|
|
|
|
|
|
if ((ret = srs_rtmp_dns_resolve(rtmp)) != 0) {
|
|
|
srs_human_trace("dns resolve failed, ret=%d", ret);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
if ((ret = srs_rtmp_connect_server(rtmp)) != 0) {
|
|
|
srs_human_trace("connect to server failed, ret=%d", ret);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
if (complex_handshake) {
|
|
|
if ((ret = srs_rtmp_do_complex_handshake(rtmp)) != 0) {
|
|
|
srs_human_trace("complex handshake failed, ret=%d", ret);
|
|
|
return ret;
|
|
|
}
|
|
|
srs_human_trace("do complex handshake success");
|
|
|
} else {
|
|
|
if ((ret = srs_rtmp_do_simple_handshake(rtmp)) != 0) {
|
|
|
srs_human_trace("simple handshake failed, ret=%d", ret);
|
|
|
return ret;
|
|
|
}
|
|
|
srs_human_trace("do simple handshake success");
|
|
|
}
|
|
|
|
|
|
if ((ret = srs_rtmp_set_connect_args(rtmp, tcUrl, swfUrl, pageUrl, args)) != 0) {
|
|
|
srs_human_trace("set connect args failed, ret=%d", ret);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
if ((ret = srs_rtmp_connect_app(rtmp)) != 0) {
|
|
|
srs_human_trace("connect vhost/app failed, ret=%d", ret);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
if ((ret = srs_rtmp_get_server_sig(rtmp, &sig)) != 0) {
|
|
|
srs_human_trace("Retrieve server ID failed, ret=%d", ret);
|
|
|
return ret;
|
|
|
}
|
|
|
if ((ret = srs_rtmp_get_server_id(rtmp, &ip, &pid, &cid)) != 0) {
|
|
|
srs_human_trace("Retrieve server ID failed, ret=%d", ret);
|
|
|
return ret;
|
|
|
}
|
|
|
if ((ret = srs_rtmp_get_server_version(rtmp, &major, &minor, &revision, &build)) != 0) {
|
|
|
srs_human_trace("Retrieve server version failed, ret=%d", ret);
|
|
|
return ret;
|
|
|
}
|
|
|
srs_human_trace("connect ok, ip=%s, server=%s/%d.%d.%d.%d, pid=%d, cid=%d",
|
|
|
ip, sig, major, minor, revision, build, pid, cid);
|
|
|
|
|
|
if ((ret = srs_rtmp_play_stream(rtmp)) != 0) {
|
|
|
srs_human_trace("play stream failed, ret=%d", ret);
|
|
|
return ret;
|
|
|
}
|
|
|
srs_human_trace("play stream success");
|
|
|
|
|
|
if (flv) {
|
|
|
// flv header
|
|
|
char header[9];
|
|
|
// 3bytes, signature, "FLV",
|
|
|
header[0] = 'F';
|
|
|
header[1] = 'L';
|
|
|
header[2] = 'V';
|
|
|
// 1bytes, version, 0x01,
|
|
|
header[3] = 0x01;
|
|
|
// 1bytes, flags, UB[5] 0, UB[1] audio present, UB[1] 0, UB[1] video present.
|
|
|
header[4] = 0x03; // audio + video.
|
|
|
// 4bytes, dataoffset
|
|
|
header[5] = 0x00;
|
|
|
header[6] = 0x00;
|
|
|
header[7] = 0x00;
|
|
|
header[8] = 0x09;
|
|
|
if ((ret = srs_flv_write_header(flv, header)) != 0) {
|
|
|
srs_human_trace("write flv header failed, ret=%d", ret);
|
|
|
return ret;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int64_t nb_packets = 0;
|
|
|
uint32_t pre_timestamp = 0;
|
|
|
int64_t pre_now = -1;
|
|
|
int64_t start_time = -1;
|
|
|
char buffer[1024];
|
|
|
for (;;) {
|
|
|
int size;
|
|
|
char type;
|
|
|
char* data;
|
|
|
uint32_t timestamp;
|
|
|
|
|
|
if ((ret = srs_rtmp_read_packet(rtmp, &type, ×tamp, &data, &size)) != 0) {
|
|
|
srs_human_trace("read rtmp packet failed, ret=%d", ret);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
if (pre_now == -1) {
|
|
|
pre_now = srs_utils_time_ms();
|
|
|
}
|
|
|
if (start_time == -1) {
|
|
|
start_time = srs_utils_time_ms();
|
|
|
}
|
|
|
|
|
|
if ((ret = srs_human_format_rtmp_packet2(buffer, sizeof(buffer), type, timestamp, data, size, pre_timestamp, pre_now, start_time, nb_packets++)) != 0) {
|
|
|
srs_human_trace("print rtmp packet failed, ret=%d", ret);
|
|
|
return ret;
|
|
|
}
|
|
|
srs_human_trace("%s", buffer);
|
|
|
|
|
|
pre_timestamp = timestamp;
|
|
|
pre_now = srs_utils_time_ms();
|
|
|
|
|
|
// we only write some types of messages to flv file.
|
|
|
int is_flv_msg = type == SRS_RTMP_TYPE_AUDIO
|
|
|
|| type == SRS_RTMP_TYPE_VIDEO || type == SRS_RTMP_TYPE_SCRIPT;
|
|
|
|
|
|
// for script data, ignore except onMetaData
|
|
|
if (type == SRS_RTMP_TYPE_SCRIPT) {
|
|
|
if (!srs_rtmp_is_onMetaData(type, data, size)) {
|
|
|
is_flv_msg = 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (flv) {
|
|
|
if (is_flv_msg) {
|
|
|
if ((ret = srs_flv_write_tag(flv, type, timestamp, data, size)) != 0) {
|
|
|
srs_human_trace("dump rtmp packet failed, ret=%d", ret);
|
|
|
return ret;
|
|
|
}
|
|
|
} else {
|
|
|
srs_human_trace("drop message type=%#x, size=%dB", type, size);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
free(data);
|
|
|
}
|
|
|
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
int main(int argc, char** argv)
|
|
|
{
|
|
|
srs_flv_t flv = NULL;
|
|
|
srs_rtmp_t rtmp = NULL;
|
|
|
|
|
|
printf("dump rtmp stream to flv file\n");
|
|
|
printf("srs(ossrs) client librtmp library.\n");
|
|
|
printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
|
|
|
printf("@refer to http://rtmpdump.mplayerhq.hu/rtmpdump.1.html\n");
|
|
|
|
|
|
int show_help = 0;
|
|
|
const char* short_options = "hxr:o:s:t:p:C:";
|
|
|
struct option long_options[] = {
|
|
|
{"rtmp", required_argument, 0, 'r'},
|
|
|
{"flv", required_argument, 0, 'o'},
|
|
|
{"swfUrl", required_argument, 0, 's'},
|
|
|
{"tcUrl", required_argument, 0, 't'},
|
|
|
{"pageUrl", required_argument, 0, 'p'},
|
|
|
{"conn", required_argument, 0, 'C'},
|
|
|
{"complex", no_argument, 0, 'x'},
|
|
|
{"help", no_argument, 0, 'h'},
|
|
|
{0, 0, 0, 0}
|
|
|
};
|
|
|
|
|
|
int opt = 0;
|
|
|
int option_index = 0;
|
|
|
while((opt = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1){
|
|
|
switch(opt){
|
|
|
case 'r':
|
|
|
rtmp_url = optarg;
|
|
|
break;
|
|
|
case 'o':
|
|
|
output_flv = optarg;
|
|
|
break;
|
|
|
case 's':
|
|
|
swfUrl = optarg;
|
|
|
break;
|
|
|
case 't':
|
|
|
tcUrl = optarg;
|
|
|
break;
|
|
|
case 'p':
|
|
|
pageUrl = optarg;
|
|
|
break;
|
|
|
case 'C':
|
|
|
if (!args) {
|
|
|
args = srs_amf0_create_object();
|
|
|
}
|
|
|
char* p = (char*)optarg;
|
|
|
parse_amf0_object(p, args);
|
|
|
break;
|
|
|
case 'x':
|
|
|
complex_handshake = 1;
|
|
|
break;
|
|
|
case 'h':
|
|
|
show_help = 1;
|
|
|
break;
|
|
|
default:
|
|
|
printf("unsupported opt.\n");
|
|
|
exit(-1);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (!rtmp_url || show_help) {
|
|
|
printf("Usage: %s -r url [-o output] [-s swfUrl] [-t tcUrl] [-p pageUrl] [-C conndata] [--complex] [-h]\n"
|
|
|
"Options:\n"
|
|
|
" --rtmp -r url\n"
|
|
|
" URL of the server and media content.\n"
|
|
|
" --flv -o output\n"
|
|
|
" Specify the output file name. If the name is − or is omitted, the stream is written to stdout.\n"
|
|
|
" --complex\n"
|
|
|
" Whether use complex handshake(srs-librtmp with ssl required).\n"
|
|
|
" --swfUrl -s url\n"
|
|
|
" URL of the SWF player for the media. By default no value will be sent.\n"
|
|
|
" --tcUrl -t url\n"
|
|
|
" URL of the target stream. Defaults to rtmp[e]://host[:port]/app/playpath.\n"
|
|
|
" --pageUrl -p url\n"
|
|
|
" URL of the web page in which the media was embedded. By default no value will be sent.\n"
|
|
|
" −−conn −C type:data\n"
|
|
|
" Append arbitrary AMF data to the Connect message. The type must be B for Boolean, N for number, S for string, O for object, or Z for null. For Booleans the data must be either 0 or 1 for FALSE or TRUE, respectively. Likewise for Objects the data must be 0 or 1 to end or begin an object, respectively. Data items in subobjects may be named, by prefixing the type with 'N' and specifying the name before the value, e.g. NB:myFlag:1. This option may be used multiple times to construct arbitrary AMF sequences. E.g.\n"
|
|
|
" −C B:1 −C S:authMe −C O:1 −C NN:code:1.23 −C NS:flag:ok −C O:0\n"
|
|
|
" -C O:1 -C NS:CONN:\" -C B:4Rg9vr0\" -C O:0\n"
|
|
|
" @remark, support a object args only.\n"
|
|
|
" --help -h\n"
|
|
|
" Print a summary of command options.\n"
|
|
|
"For example:\n"
|
|
|
" %s -r rtmp://127.0.0.1:1935/live/livestream -o output.flv\n"
|
|
|
" %s -h\n",
|
|
|
argv[0], argv[0], argv[0]);
|
|
|
exit(-1);
|
|
|
}
|
|
|
|
|
|
srs_human_trace("rtmp url: %s", rtmp_url);
|
|
|
srs_human_trace("handshake: %s", (complex_handshake? "complex" : "simple"));
|
|
|
srs_human_trace("swfUrl: %s", swfUrl);
|
|
|
srs_human_trace("pageUrl: %s", pageUrl);
|
|
|
srs_human_trace("tcUrl: %s", tcUrl);
|
|
|
if (output_flv) {
|
|
|
srs_human_trace("flv output path: %s", output_flv);
|
|
|
} else {
|
|
|
srs_human_trace("output to console");
|
|
|
}
|
|
|
|
|
|
rtmp = srs_rtmp_create(rtmp_url);
|
|
|
if (output_flv) {
|
|
|
flv = srs_flv_open_write(output_flv);
|
|
|
}
|
|
|
|
|
|
int ret = 0;
|
|
|
if ((ret = do_proxy(rtmp, flv)) != 0) {
|
|
|
srs_human_trace("Dump RTMP failed, ret=%d", ret);
|
|
|
}
|
|
|
|
|
|
srs_rtmp_destroy(rtmp);
|
|
|
srs_flv_close(flv);
|
|
|
srs_human_trace("completed");
|
|
|
|
|
|
return 0;
|
|
|
}
|