/** * 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 #include #include #include #include #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; }