mirror of
				https://github.com/ossrs/srs.git
				synced 2025-03-09 15:49:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			350 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			350 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /**
 | ||
|  * 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;
 | ||
| }
 |