File Coverage

blib/lib/RTMP/Client.pm
Criterion Covered Total %
statement 30 443 6.7
branch 0 180 0.0
condition 0 12 0.0
subroutine 10 49 20.4
pod 35 39 89.7
total 75 723 10.3


line stmt bran cond sub pod time code
1             package RTMP::Client;
2 1     1   32275 use Socket qw(:all);
  1         4226  
  1         10164  
3 1     1   17 use Fcntl;
  1         2  
  1         669  
4 1     1   1786 use Time::HiRes qw(gettimeofday);
  1         3029  
  1         8  
5              
6 1     1   283 use 5.008008;
  1         4  
  1         46  
7 1     1   7 use warnings;
  1         3  
  1         2018  
8              
9             our $VERSION = '0.04';
10              
11             require Exporter;
12             our @ISA = qw(Exporter);
13             our @EXPORT_OK = qw(rtmp_connect rtmp_play rtmp_call print_hex my_recv);
14             our $rtmp_packet_length = 12;
15             our $debug_flag = 0;
16              
17             =head1 NAME
18              
19             RTMP::Client - A Simple RTMP client
20              
21             =head1 SYNOPSIS
22              
23             use RTMP::Client qw(rtmp_connect rtmp_play rtmp_call);
24              
25             print "connect success\n" if rtmp_connect('192.168.1.1', '1935', 'live/23');
26             rtmp_play('MainB_1', '-1.0', '-1.0');
27             rtmp_call('YourFunc', 'YourARGV');
28              
29             =head1 DESCRIPTION
30              
31             This is a simple RTMP client without video or audio decode.
32             It implemented in pure PERL including packing Adobe amf packages.
33              
34             =head1 METHODS
35              
36             =head2 rtmp_connect($rtmp_server_address, $server_port, $application_name)
37              
38             Just like the 'NetConnection.connect()' function in ActionScript, with args are set in different way.
39              
40             =cut
41              
42             sub rtmp_connect
43             {
44 0     0 1   my ($server_addr, $server_port, $app_name ) = @_;
45 0 0         die 'app_name MUST NOT begin with \'/\' ' if substr($app_name, 0 , 1) eq '/';
46              
47 0           my $ipaddress;
48 0 0         if ($server_addr =~ /^[\d\.]+$/ )
49 0           { $ipaddress = inet_aton($server_addr); } # ip addr here
50             else
51 0           { $ipaddress = gethostbyname($server_addr); } # domain addr here
52 0           my $address = sockaddr_in($server_port, $ipaddress);
53 0 0         socket(SOCK, AF_INET, SOCK_STREAM, IPPROTO_TCP) || die $!;
54 0 0         connect(SOCK, $address) || die $!;
55             #my $fh_flag = fcntl(SOCK, F_GETFL,0);
56             #$fh_flag |= O_NONBLOCK;
57             #my $fh_flag = fcntl(SOCK, F_SETFL, $fh_flag);
58              
59 0 0         die 'handshake fail' unless rtmp_handshake();
60              
61 0           my $amf_body = pack_amf_string('connect');
62 0           $amf_body.= pack_amf_number('1.0');
63 0           $amf_body.= pack_amf_object_start();
64 0           $amf_body.= pack_amf_attribute_name('app');
65 0           $amf_body.= pack_amf_string($app_name);
66 0           $amf_body.= pack_amf_attribute_name('flashVer');
67 0           $amf_body.= pack_amf_string('LNX 10,0,32,18');
68 0           $amf_body.= pack_amf_attribute_name('swfUrl');
69 0           $amf_body.= pack_amf_string('http://sina/perl/rtmp/client-fate.swf');
70 0           $amf_body.= pack_amf_attribute_name('tcUrl');
71 0           $amf_body.= pack_amf_string("rtmp://$server_addr/$app_name");
72 0           $amf_body.= pack_amf_attribute_name('fpad');
73 0           $amf_body.= pack_amf_boolean('false');
74 0           $amf_body.= pack_amf_attribute_name('capabilities');
75 0           $amf_body.= pack_amf_number('15.0');
76 0           $amf_body.= pack_amf_attribute_name('audioCodecs');
77 0           $amf_body.= pack_amf_number('3191.0');
78 0           $amf_body.= pack_amf_attribute_name('videoCodecs');
79 0           $amf_body.= pack_amf_number('252.0');
80 0           $amf_body.= pack_amf_attribute_name('videoFunction');
81 0           $amf_body.= pack_amf_number('1.0');
82 0           $amf_body.= pack_amf_attribute_name('pageUrl');
83 0           $amf_body.= pack_amf_string('http://sina/perl/rtmp/client-fate.html');
84 0           $amf_body.= pack_amf_attribute_name('objectEncoding');
85 0           $amf_body.= pack_amf_number('0.0');
86 0           $amf_body.= pack_amf_object_end();
87              
88             # set object id 3
89 0           my $amf = pack_amf_body_to_chunks($amf_body, '00000011', '00000000', '14');
90 0           my_send_bin($amf);
91 0           for (1..5)
92             {
93 0           my @rtmp_content = my_recv_a_msg();
94 0           analysis_rtmp_msg(@rtmp_content);
95             }
96              
97 0           $amf = pack('H*', '025154620000040500000000002625a0');
98 0           my_send_bin($amf);
99              
100             # user control message -> set buffer length -> set stream 0 buffer to 1000
101 0           $amf = pack('H*', '4200000000000a04000300000000000003e8');
102 0           my_send_bin($amf);
103              
104 0           $amf_body = pack_amf_string('createStream');
105 0           $amf_body.= pack_amf_number('2.0');
106 0           $amf_body.= pack_amf_null();
107 0           $amf = pack_amf_body_to_chunks($amf_body, '00000011', '00000000', '14');
108 0           my_send_bin($amf);
109              
110 0           $amf = pack('H*', 'c2000300000001000003e8');
111 0           my_send_bin($amf);
112 0           for (1..1)
113             {
114 0           my @rtmp_content = my_recv_a_msg();
115 0           analysis_rtmp_msg(@rtmp_content);
116             }
117              
118 0           return 1;
119             }
120              
121             =head2 rtmp_play($stream_or_file_name, $play_type, $length, $interval_call_hook_function, $hook_function)
122              
123             Just like the 'NetStream.play()' function in ActionScript, with args are set in different way.
124             You can use the last two args or not.
125              
126             =cut
127              
128             sub rtmp_play
129             {
130 0     0 1   my ($stream_name, $play_type, $length, $recieve_loop_time, $hook_function) = @_;
131 0           my $amf_body = pack_amf_string('play');
132 0           $amf_body.= pack_amf_number('0.0');
133 0           $amf_body.= pack_amf_null();
134 0           $amf_body.= pack_amf_string($stream_name);
135 0           $amf_body.= pack_amf_number($play_type);
136 0           $amf_body.= pack_amf_number($length);
137             #my $amf = pack_amf_body_to_chunks($amf_body, '00001000', '01000000', '14');
138 0           my $amf = pack('H*','0800372400002e110100000000020004706c6179000000000000000000050200074d61696e425f3100c08f40000000000000c08f400000000000');
139 0           my_send_bin($amf);
140              
141 0           my_recv_nostop($recieve_loop_time, $hook_function);
142             }
143              
144             =head2 rtmp_call($stream_or_file_name, $play_type, $length, $file_path_to_store_the_data_received)
145              
146             Just like the 'NetStream.call()' function in ActionScript, with args are set in different way.
147              
148             =cut
149              
150             sub rtmp_call
151             {
152 0     0 1   my ($func_name,$select_option) = @_;
153             #return unless $select_option =~ /^[A-Z]$/;
154 0           my $amf_body = pack_amf_string($func_name);
155 0           $amf_body.= pack_amf_number('1.0');
156 0           $amf_body.= pack_amf_null();
157             #$amf.= pack_amf_string($select_option);
158 0           $amf_body.= pack_amf_string($select_option);
159             #my $amf = pack_amf_body_to_chunks($amf_body, '01000011', '01000000', '11');
160             #print_hex($amf);
161 0           my $amf = pack('H*','43010d640000271100');
162 0           $amf.= $amf_body;
163 0           my_send_bin($amf);
164              
165 0           my @rtmp_content = my_recv_a_msg();
166 1     1   1571 my $string = do { use bytes;length $rtmp_content[1]; };
  1         20  
  1         8  
  0            
  0            
167 0           return $string;
168              
169             }
170              
171              
172             =head1 EXAMPLES
173              
174             use RTMP::Client qw(rtmp_connect rtmp_play rtmp_call);
175              
176             Speed Detection
177              
178             report download speed every 5 secs.
179              
180             print "connect success\n" if rtmp_connect('192.168.1.1', '1935', 'live/23');
181             my $report_time = 5;
182             rtmp_play('MainB_1', '-1.0', '-1.0', $report_time, \&speed_detector);
183             sub speed_detector
184             {
185             my $rev_length = shift;
186             my $speed = $rev_length / 1024 / $report_time;
187             if ($speed > 3)
188             {
189             my $cur_time = strftime("%F_%T", localtime);
190             print $cur_time, "\t", $speed, "\tKbytes/s\n";
191             }
192             else
193             {
194             print "too slow !\n";
195             }
196             }
197              
198            
199             Save to File
200              
201             do things like "rtmpdump".L
202              
203             print "connect success\n" if rtmp_connect('192.168.1.1', '1935', 'live/23');
204             my $loop_time = 10;
205             rtmp_play('MainB_1', '-1.0', '-1.0', $report_time, \&save_to_file);
206             sub save_to_file
207             {
208             my $rev_length = shift;
209             my $rev_binary = shift;
210             open my $fh,">>","/root/rtmp_dump.bin";
211             binmode $fh;
212             print $fh $rev_binary;
213             close $fh;
214             }
215              
216              
217             =head1 SOME INTERNAL METHODS
218              
219             =head2 rtmp_handshake()
220              
221             No args need. Called in function rtmp_connect().
222              
223             =cut
224              
225             sub rtmp_handshake
226             {
227 0     0 1   my @c0 = qw(03);
228 0           my_send_hex(\@c0);
229              
230 0           my @c1 = qw(00 00 00 00); # time - 4 bytes
231 0           push @c1, qw(00 00 00 00); # zero - 4 bytes (do not MUST!)
232 0           for (1..1528) { push @c1, '99';}
  0            
233 0           my_send_hex(\@c1);
234              
235             #print "start handshack...\n";
236 0           my $s0 = my_recv(1);
237 0           my $s1 = my_recv(1536);
238 0           my $s2 = my_recv(1536);
239 0           my $c2 = $s1;
240 0           my_send_bin($c2);
241             #print "handshack success.\n";
242              
243 0           return 1;
244             }
245              
246             =head2 pack_amf_body_to_chunks($string, $object_id, $stream_id, $type)
247              
248             Output a available binary amf packet.
249             Works on amf message body, just like add a right amf header before the message body.
250              
251             =cut
252              
253             sub pack_amf_body_to_chunks
254             {
255 0     0 1   my ($body, $size_and_object_id, $stream_id, $type) = @_;
256 1     1   562 my $body_length = do { use bytes; length $body; };
  1         4  
  1         48  
  0            
  0            
257              
258 0           my $header = pack('B8', $size_and_object_id); # 2 = the size of the header{here: 12} 6 = object id{here: 3}
259 0           $header.= pack('H6', '000000'); # timestamp (big-endian integer) [header size >= 4 bytes]
260 0           $header.= substr( pack('N', $body_length), 1); # length of the object body (big-endian integer) [>= 8 bytes]
261 0           $header.= pack('H2', $type); # content type [>= 8 bytes] {0x14: Function call, 0x09: Video data, 0x08: Audio data}
262 0           $header.= pack('H8', $stream_id); # a stream id (32 bit integer that is little-endian encoded) [only = 12 bytes]
263              
264 0           my $need_chunk_num = ( $body_length - ( $body_length % 128 ) ) / 128;
265 0           $need_chunk_num++;
266 0           my $output = $header;
267 0           for (1..$need_chunk_num)
268             {
269 0           my $seek_flag = ($_ - 1) * 128 ;
270 0           $output.= substr($body, $seek_flag, 128);
271 0           $output.= pack('H2', 'c3');
272             }
273 0           $output = substr( $output, 0, -1);
274 0           return $output;
275             }
276              
277             =head2 pack_amf_object_start()
278              
279             =cut
280              
281             sub pack_amf_object_start
282             {
283 0     0 1   my $output = pack('H2', '03');
284 0           return $output;
285             }
286              
287             =head2 pack_amf_object_end()
288              
289             =cut
290              
291             sub pack_amf_object_end
292             {
293 0     0 1   my $output = pack('H6', '000009');
294 0           return $output;
295             }
296              
297             =head2 pack_amf_attribute_name($string)
298              
299             It packs a attribute_name which less than 65536 bytes or return null.
300              
301             =cut
302              
303             sub pack_amf_attribute_name
304             {
305 0     0 1   my ($string) = (@_);
306 1     1   597 my $length = do { use bytes; length $string; };
  1         3  
  1         5  
  0            
  0            
307 0 0         return '' if $length > 65536;
308 0           my $output = pack('n', $length);
309 0           $output.= $string;
310 0           return $output;
311             }
312              
313             =head2 pack_amf_number($double)
314              
315             Return 9 bytes binary data.
316              
317             =cut
318              
319             sub pack_amf_number
320             {
321 0     0 1   my ($number) = (@_);
322 0           my $output = pack('H2', '00');
323 0           $output.= join "", reverse split//, pack('d', $number);
324 0           return $output;
325             }
326              
327             =head2 pack_amf_boolean($boolean)
328              
329             Return 2 bytes binary data.
330              
331             =cut
332              
333             sub pack_amf_boolean
334             {
335 0     0 1   my ($boolean) = (@_);
336 0           my $output = pack('H2', '01');
337 0 0         $output.= pack('H2', '00') if $boolean eq 'false';
338 0 0         $output.= pack('H2', '01') if $boolean eq 'true';
339 0           return $output;
340             }
341              
342             =head2 pack_amf_string($string)
343              
344             it can pack a string which less than 65536 bytes or it return null.
345             There will be a long string packer in future.
346              
347             =cut
348              
349             sub pack_amf_string
350             {
351 0     0 1   my ($string) = (@_);
352 1     1   351 my $length = do { use bytes; length $string; };
  1         3  
  1         6  
  0            
  0            
353 0 0         return '' if $length > 65536; #todo: alarm or switch into long_string function.
354 0           my $output = pack('H2', '02');
355 0           $output.= pack('n', $length);
356 0           $output.= $string;
357 0           return $output;
358             }
359              
360             =head2 pack_amf_boolean($boolean)
361              
362             Return 1 byte binary data.
363              
364             =cut
365              
366             sub pack_amf_null
367             {
368 0     0 0   return pack('H2', '05');
369             }
370              
371             =head2 my_recv_a_chunk()
372              
373             Recieve a rtmp chunk.
374              
375             =cut
376              
377             sub my_recv_a_chunk
378             {
379             # get the first byte include fmt and chunk stream id
380 0     0 1   my $fmt_and_chunk_id = my_recv(1);
381 0           $fmt_and_chunk_id = unpack('B8', $fmt_and_chunk_id);
382 0           dprint("Chunk fmt: $fmt_and_chunk_id\n");
383              
384             # get chunk_id
385 0           my $chunk_id = substr( $fmt_and_chunk_id, 2, 6);
386 0 0         if ($chunk_id eq '000000')
    0          
387             {
388             # havn`t test
389 0           my $buf = dec(my_recv(1));
390 0           $chunk_id = $buf + 64;
391             }
392             elsif ($chunk_id eq '000001')
393             {
394             # havn`t test
395 0           my $buf = dec(my_recv(1));
396 0           $chunk_id = $buf + 64;
397 0           $buf = dec(my_recv(1));
398 0           $chunk_id += $buf * 256;
399             }
400             else
401             {
402 0           $chunk_id = oct('0b'.$chunk_id);
403             }
404              
405             # get chunk_header_length
406 0           my $fmt = substr( $fmt_and_chunk_id, 0, 2);
407 0 0         if ($fmt eq '00')
    0          
    0          
    0          
408             {
409             #Chunks of Type 0 are 11 bytes long. This type MUST be used at the
410             #start of a chunk stream, and whenever the stream timestamp goes
411             #backward (e.g., because of a backward seek).
412 0           my $chunk_header = my_recv(11);
413 0 0         print('Chunk header: ') if $debug_flag;
414 0 0         print_hex($chunk_header) if $debug_flag;
415              
416             #the absolute timestamp of the message is sent here.
417             #If the timestamp is greater than or equal to 16777215
418             #(hexadecimal 0x00ffffff), this value MUST be 16777215, and the
419             #‘extended timestamp header’ MUST be present. Otherwise, this value
420             #SHOULD be the entire timestamp.
421 0           my $chunk_timestamp = dec(substr($chunk_header, 0, 3)); #todo: absolute timestamp for what?
422 0 0         if ($chunk_timestamp >= 16777215)
423             {
424 0           $chunk_extended_timestamp = dec(my_recv(4));
425 0           $chunk_timestamp = $chunk_extended_timestamp + 16777215;
426             }
427 0           reset_rtmp_timer($chunk_id); #todo: means reset chunk timestamp here?
428              
429             #Note that this is generally not the same as the length of the chunk
430             #payload. The chunk payload length is the maximum chunk size for all
431             #but the last chunk, and the remainder (which may be the entire
432             #length, for small messages) for the last chunk.
433 0           my $chunk_msg_length = dec(substr($chunk_header, 3, 3));
434 0           set_rtmp_chunk_msg_length($chunk_id, $chunk_msg_length);
435 0           my $chunk_msg_type_id = dec(substr($chunk_header, 6, 1));
436 0           set_rtmp_chunk_msg_type_id($chunk_id, $chunk_msg_type_id);
437 0           my $chunk_msg_stream_id = dec(substr($chunk_header, 7, 4));
438 0           set_rtmp_chunk_msg_stream_id($chunk_id, $chunk_msg_stream_id);
439             }
440             elsif ($fmt eq '01')
441             {
442             #Chunks of Type 1 are 7 bytes long. The message stream ID is not
443             #included; this chunk takes the same stream ID as the preceding chunk.
444             #Streams with variable-sized messages (for example, many video
445             #formats) SHOULD use this format for the first chunk of each new
446             #message after the first.
447 0           my $chunk_header = my_recv(7);
448              
449 0           my $chunk_timestamp_deta = dec(substr($chunk_header, 0, 3)); #for eval the bandwidth, i do not use it.
450 0 0         if ($chunk_timestamp >= 16777215)
451             {
452 0           $chunk_extended_timestamp = dec(my_recv(4));
453 0           $chunk_timestamp = $chunk_extended_timestamp + 16777215;
454             }
455 0           my $chunk_message_length = dec(substr($chunk_header, 3, 3));
456 0           set_rtmp_chunk_msg_length($chunk_id, $chunk_message_length);
457 0           my $chunk_message_type_id = dec(substr($chunk_header, 6, 1));
458 0           set_rtmp_chunk_msg_type_id($chunk_id, $chunk_message_type_id);
459             }
460             elsif ($fmt eq '10')
461             {
462             #Chunks of Type 2 are 3 bytes long. Neither the stream ID nor the
463             #message length is included; this chunk has the same stream ID and
464             #message length as the preceding chunk. Streams with constant-sized
465             #messages (for example, some audio and data formats) SHOULD use this
466             #format for the first chunk of each message after the first.
467 0           my $chunk_header = my_recv(3);
468              
469 0           my $chunk_timestamp_deta = dec(substr($chunk_header, 0, 3)); #for eval the bandwidth, i do not use it.
470 0 0         if ($chunk_timestamp >= 16777215)
471             {
472 0           $chunk_extended_timestamp = dec(my_recv(4));
473 0           $chunk_timestamp = $chunk_extended_timestamp + 16777215;
474             }
475             }
476             elsif ($fmt eq '11')
477             {
478             #Chunks of Type 3 have no header. Stream ID, message length and
479             #timestamp delta are not present; chunks of this type take values from
480             #the preceding chunk. When a single message is split into chunks, all
481             #chunks of a message except the first one, SHOULD use this type.
482             }
483              
484 0           my $msg_length = get_rtmp_chunk_msg_length($chunk_id);
485 0           my $msg_type_id = get_rtmp_chunk_msg_type_id($chunk_id);
486              
487             # if this is a Protocol Control Messages
488             #Protocol control messages SHOULD have message stream ID 0(called as
489             #control stream) and chunk stream ID 2
490 0 0 0       if ($chunk_id == 2 and get_rtmp_chunk_msg_stream_id($chunk_id) == 0)
491             {
492 0           dprint('protocol_control_message', 3);
493             #Set Chunk Size
494 0 0         if ($msg_type_id == 1)
    0          
    0          
    0          
    0          
    0          
495             {
496 0           dprint('Set Chunk Size',6);
497 0           my $new_chunk_size = dec(my_recv(4));
498 0           set_rtmp_client_chunk_size($new_chunk_size);
499 0           dprint("new chunk size is $new_chunk_size", 6);
500             # my $set_chunk_response =
501             # pack('H2','02')
502             # . get_rtmp_timer($chunk_id, '3bytes')
503             # . pack('H6','000004')
504             # . pack('H2','01')
505             # . pack('H8','00000000')
506             # . pack('H8','00000080');
507             # my_send_bin($set_chunk_response);
508             }
509             #Abort Message
510             elsif ($msg_type_id == 2)
511             {
512 0           dprint('Abort Message',6);
513             }
514             # Acknowledgement
515             elsif ($msg_type_id == 3)
516             {
517 0           dprint('Acknowledgement',6);
518             }
519             # User Control Message
520             elsif ($msg_type_id == 4)
521             {
522 0           dprint('User Control Message',6);
523 0           my $event_type = dec(my_recv(2));
524 0 0         if($event_type == 0)
    0          
    0          
    0          
    0          
    0          
    0          
    0          
525             {
526 0           dprint('Stream Begin', 9);
527 0           my $begin_stream_id = dec(my_recv(4));
528 0           dprint("Begin Stream ID is $begin_stream_id", 9);
529             }
530             elsif($event_type == 1)
531             {
532 0           dprint('Stream EOF', 9);
533             }
534             elsif($event_type == 2)
535             {
536 0           dprint('Stream Dry', 9);
537             }
538             elsif($event_type == 3)
539             {
540 0           dprint('Set Buffer Length', 9);
541             }
542             elsif($event_type == 4)
543             {
544 0           dprint('StreamIsRecorded', 9);
545             }
546             elsif($event_type == 5)
547             {
548 0           dprint('user contorl error recving', 9);
549             }
550             elsif($event_type == 6)
551             {
552 0           dprint('PingRequest', 9);
553 0           my $ping_timestamp = my_recv(4);
554 0           my $ping_response =
555             pack('H2','02')
556             . get_rtmp_timer($chunk_id, '3bytes')
557             . pack('H6','000004')
558             . pack('H2','07')
559             . pack('H8','00000000')
560             . $ping_timestamp;
561 0           my_send_bin($ping_response);
562             }
563             elsif($event_type == 7)
564             {
565 0           dprint('PingResponse', 9);
566             }
567             else
568             {
569 0           print "user contorl error recving!!!!\n";
570             }
571             }
572             # Window Acknowledgement Size
573             elsif ($msg_type_id == 5)
574             {
575 0           dprint('Window Acknowledgement Size',6);
576 0           my $server_window_acknowledgement = dec(my_recv($msg_length));
577 0           set_rtmp_peer_window($server_window_acknowledgement); # could not work on this
578 0           dprint("peer Window Size is $server_window_acknowledgement now", 6);
579             }
580             # Set Peer Bandwidth
581             elsif ($msg_type_id == 6)
582             {
583 0           dprint('Set Peer Bandwidth',6);
584 0           my $buf = my_recv($msg_length);
585 0           my $set_window_size = dec(substr($buf, 0, 4));
586 0           my $limit_type = dec(substr($buf, 4, 1)); # 0-hard, 1-soft, 2-dunamic
587 0           set_rtmp_window($set_window_size, $limit_type);
588 0           dprint("peer set my Sindow Size to $set_window_size with limit type $limit_type", 6);
589 0           my $rtmp_packet_window_acknowledgement_size =
590             pack('H2','02')
591             . get_rtmp_timer($chunk_id, '3bytes')
592             . pack('H6','000004')
593             . pack('H2','05')
594             . pack('H8','00000000')
595             . substr($buf, 0, 4);
596 0           my_send_bin($rtmp_packet_window_acknowledgement_size);
597             }
598 0           return 'rtmp_protocol_control_message';
599             }
600              
601 0 0         if($msg_type_id == 20)
602             {
603 0           dprint('Command message in AMF0', 3);
604             }
605 0           my $chunk_size = get_rtmp_client_chunk_size(); # all rtmp chunk stream share one chunk_size here.
606              
607 0 0         if($msg_length <= $chunk_size)
608             {
609             #dprint("payload 1:$msg_length");
610 0           my $rtmp_msg = my_recv($msg_length);
611 0           put_rtmp_msg($chunk_id, $rtmp_msg);
612 0           return $chunk_id;
613             }
614             else
615             {
616 0 0         our $msg_length_remain = get_rtmp_chunk_msg_length($chunk_id) unless defined $msg_length_remain;
617 0 0         $msg_length_remain = get_rtmp_chunk_msg_length($chunk_id) if $msg_length_remain <= 0;
618 0 0         if ($msg_length_remain <= $chunk_size)
619             {
620             #dprint("payload 2:$msg_length_remain");
621 0           my $rtmp_msg = my_recv($msg_length_remain);
622 0           put_rtmp_msg($chunk_id, $rtmp_msg);
623 0           $msg_length_remain = 0;
624 0           return $chunk_id;
625             }
626             else
627             {
628             #dprint("payload 3:$chunk_size");
629 0           my $rtmp_msg = my_recv($chunk_size);
630 0           put_rtmp_msg($chunk_id, $rtmp_msg);
631 0           $msg_length_remain = $msg_length_remain - $chunk_size;
632 0           return 'rtmp_msg_recv_piece';
633             }
634             }
635             }
636              
637             =head2 my_recv_a_msg()
638              
639             Recieve a rtmp message.
640              
641             =cut
642              
643             sub my_recv_a_msg
644             {
645 0 0   0 1   our $msg_counter unless defined $msg_counter;
646 0           my $chunk_id = 0;
647 0           my $chunk_count = 0;
648 0           my $limit_chunk = 9; # this is a limit by me. not by the RTMP
649 0           while(1)
650             {
651 0           $chunk_count++;
652             #print "[", "-" x 5, " $chunk_count ", "-" x 5 ,"]\n";
653 0           $chunk_id = my_recv_a_chunk();
654             #die "limit chunk already recv.\n" if $chunk_count == $limit_chunk;
655 0 0         if ($chunk_id =~ /[\d]+/)
    0          
656             {
657 0           my $rtmp_msg = get_rtmp_msg($chunk_id);
658 0           my $rtmp_msg_type_id = get_rtmp_chunk_msg_type_id($chunk_id);
659              
660 0           my $rtmp_msg_type_name = '';
661 0 0 0       if ($rtmp_msg_type_id == 8)
    0 0        
    0 0        
    0          
    0          
    0          
662             {
663 0           $rtmp_msg_type_name = 'Audio';
664             }
665             elsif ($rtmp_msg_type_id == 9)
666             {
667 0           $rtmp_msg_type_name = 'Video';
668             }
669             elsif ($rtmp_msg_type_id == 20 or $rtmp_msg_type_id == 17)
670             {
671 0           $rtmp_msg_type_name = 'Command';
672             }
673             elsif ($rtmp_msg_type_id == 18 or $rtmp_msg_type_id == 15)
674             {
675 0           $rtmp_msg_type_name = 'Data';
676             }
677             elsif ($rtmp_msg_type_id == 19 or $rtmp_msg_type_id == 16)
678             {
679 0           $rtmp_msg_type_name = 'SharedObject';
680             }
681             elsif ($rtmp_msg_type_id == 22)
682             {
683 0           $rtmp_msg_type_name = 'Aggregate';
684             }
685             else
686             {
687 0           $rtmp_msg_type_name = "SomethingElse[$rtmp_msg_type_id]";
688             }
689              
690 0           $msg_counter++;
691 0 0         print "=" x 50, " $msg_counter - $rtmp_msg_type_name ", "=" x 50 ,"\n" if $debug_flag;
692              
693 0           reset_rtmp_msg($chunk_id);
694 0           return ($rtmp_msg_type_id, $rtmp_msg);
695             }
696             elsif($chunk_id eq 'rtmp_protocol_control_message')
697             {
698 0           return '';
699             }
700             }
701             }
702              
703             =head2 my_recv_nostop(\&sub)
704              
705             Wait until recieved bytes, then return it.
706              
707             =cut
708              
709             sub my_recv_nostop
710             {
711 0     0 1   my ($report_loop_time, $function) = @_;
712 0 0         return unless ref $function eq 'CODE';
713              
714 0           my $how_many_btyes_recieved_total = 0;
715 0           my $how_many_btyes_recieved_noack = 0;
716 0           my $how_many_btyes_recieved_noreport = 0;
717 0           my $time_flag = time;
718 0           my $report_time_count = 0;
719            
720 0           my $recieved_binary;
721 0           while(1)
722             {
723 0           my @rtmp_content = my_recv_a_msg();
724 0           analysis_rtmp_msg(@rtmp_content);
725              
726             #my $how_many_btyes_recieved = sysread(SOCK, $buf, 4096); #or die "sysread: $!";
727             #$bytes .= $buf;
728 0           $recieved_binary = $rtmp_content[1];
729 1     1   3320 my $how_many_btyes_recieved = do {use bytes; length($rtmp_content[1]);};
  1         3  
  1         5  
  0            
  0            
730 0           $how_many_btyes_recieved_total += $how_many_btyes_recieved;
731 0           $how_many_btyes_recieved_noack += $how_many_btyes_recieved;
732 0           $how_many_btyes_recieved_noreport += $how_many_btyes_recieved;
733 0 0         if ( $how_many_btyes_recieved_noack >= get_rtmp_window() )
734             {
735 0           dprint("send acknowlege to server.");
736 0           $how_many_btyes_recieved_noack = 0;
737 0           my $send_bin_tmp = join "", reverse split//, pack('I', $how_many_btyes_recieved_total);
738             #my $send_bin = pack('H*','4200000000000403') . $send_bin_tmp;
739 0           my $send_bin =
740             pack('H2','02')
741             . get_rtmp_timer(2, '3bytes')
742             . pack('H6','000004')
743             . pack('H2','03')
744             . pack('H8','00000000')
745             . $send_bin_tmp;
746 0           my_send_bin($send_bin);
747             }
748              
749 0 0         if (time - $time_flag >= $report_loop_time)
750             {
751 0           $time_flag = time;
752 0           $report_time_count++;
753 0           &$function($how_many_btyes_recieved_noreport, $recieved_binary);
754 0           $how_many_btyes_recieved_noreport = 0;
755             }
756             }
757 0           return 0;
758             }
759              
760             =head2 my_recv($int_wanted_length, $int_time_out)
761              
762             Wait $int_time_out Seconds, or Recieve $int_wanted_length bytes, then return it.
763              
764             =cut
765              
766             sub my_recv
767             {
768 0     0 1   my ($wanted_length, $time_out) = @_;
769 0 0         return unless $wanted_length =~ /^[1-9][\d]*$/;
770 0 0         $time_out = 3 unless $time_out;
771              
772 0           my $start_time = time;
773 0           my $bytes = '';
774 0           while(1)
775             {
776 0 0         if (time - $start_time >= $time_out)
777             {
778 0           print "func: my_recv(), timeout for $time_out Seconds\n";
779 0           return '';
780             }
781 0           my $buf = '';
782 0           my $success_length = sysread(SOCK, $buf, $wanted_length);
783 0 0         next unless $success_length; # for no-block IO
784 0           $bytes .= $buf;
785 0           $wanted_length = $wanted_length - $success_length;
786 0 0         last if $wanted_length <= 0;
787             }
788 0           return $bytes;
789             }
790              
791             =head2 my_send_bin($binary_data)
792              
793             Send binary data to server.
794              
795             =cut
796              
797             sub my_send_bin
798             {
799 0     0 1   my ($bin_string) = @_;
800 0           my $sent = syswrite(SOCK, $bin_string);
801 0           return $sent;
802             }
803              
804             =head2 my_send_hex(@array_with_hex)
805              
806             Convert hex array to binary ,then send them to server.
807              
808             =cut
809              
810             sub my_send_hex
811             {
812 0     0 1   my ($ref_hex_array) = @_;
813 0           my $send_str = '';
814 0           foreach (@$ref_hex_array)
815             {
816 0           my $one_byte = '0x'.$_;
817 0           $send_str.= pack('C', oct($one_byte));
818             }
819 0           my $sent = syswrite(SOCK, $send_str);
820 0           return $sent;
821             }
822              
823             =head2 print_hex($binary_data)
824              
825             Print binary data in a readable format.
826              
827             =cut
828              
829             sub print_hex
830             {
831 0     0 1   my ($bytes) = @_;
832 0           my $col_counter = 0;
833 0           my @bytes = split //, $bytes;
834 0           foreach (@bytes)
835             {
836 0           $col_counter++;
837 0           printf "%02x ", ord($_);
838 0 0         if($col_counter == 16)
839             {
840 0           $col_counter = 0;
841 0           print "\n";
842             }
843             }
844 0           print "\n";
845             }
846              
847             =head2 rtmp_timer($int_chunk_id)
848              
849             Return rtmp timestamp of the chunk stream id in string format.
850             From Adobe RTMP Spec:
851             1.Timestamps in RTMP Chunk Stream are given as an integer number of milliseconds.
852             2.each Chunk Stream will start with a timestamp of 0, but this is not required.
853             3.Timestamps MUST be monotonically increasing, and SHOULD be linear in time.
854              
855             =cut
856              
857             sub get_rtmp_timer
858             {
859 0     0 0   my ($chunk_id, $format) = @_;
860 0 0         return unless $chunk_id =~ /^[1-9][\d]*$/;
861              
862 0           my $output;
863 0           my ($sec, $microsec) = gettimeofday;
864 0           my $now_time = $sec . substr($microsec, 0, 3);
865 0 0         our @rtmp_timer_start unless defined @rtmp_timer_start;
866 0 0         if (defined $rtmp_timer_start[$chunk_id])
867             {
868 0           my $rtmp_timestamp = $now_time - $rtmp_timer_start[$chunk_id];
869 0 0         $rtmp_timestamp = 0 if $rtmp_timestamp < 0; # keep it. sometimes happen.
870 0           $output = $rtmp_timestamp;
871             }
872             else
873             {
874 0           $rtmp_timer_start[$chunk_id] = $now_time;
875 0           $output = 0;
876             }
877              
878 0 0         if($format eq '3bytes')
879             {
880 0           $output = join "", reverse split//, pack('I',$output);
881 0           $output = substr($output, 1, 3);
882             }
883              
884 0           return $output;
885             }
886              
887             =head2 reset_rtmp_timer($chunk_id)
888              
889             =cut
890              
891             sub reset_rtmp_timer
892             {
893 0     0 1   my $chunk_id = shift;
894 0 0         return unless $chunk_id =~ /^[1-9][\d]*$/;
895              
896 0           my ($sec, $microsec) = gettimeofday;
897 0           my $now_time = $sec . substr($microsec, 0, 3);
898 0 0         our @rtmp_timer_start unless defined @rtmp_timer_start;
899 0           $rtmp_timer_start[$chunk_id] = $now_time;
900 0           return 1;
901             }
902              
903             =head2 reset_rtmp_timer($chunk_id, $chunk_message_length)
904              
905             =cut
906              
907             sub set_rtmp_chunk_msg_length
908             {
909 0     0 0   my ($chunk_id, $chunk_message_length) = @_;
910 0 0         return unless $chunk_id =~ /^[1-9][\d]*$/;
911              
912 0 0         our @rtmp_msg_length unless defined @rtmp_msg_length;
913 0           $rtmp_msg_length[$chunk_id] = $chunk_message_length;
914 0           return 1;
915             }
916              
917             =head2 get_rtmp_chunk_msg_length($chunk_id)
918              
919             =cut
920              
921             sub get_rtmp_chunk_msg_length
922             {
923 0     0 1   my ($chunk_id) = @_;
924 0 0         return unless $chunk_id =~ /^[1-9][\d]*$/;
925 0 0         print 'get msg length before set.' unless defined @rtmp_msg_length;
926 0           return $rtmp_msg_length[$chunk_id];
927             }
928              
929             =head2 set_rtmp_chunk_msg_type_id($chunk_id, $chunk_message_type_id)
930              
931             =cut
932              
933             sub set_rtmp_chunk_msg_type_id
934             {
935 0     0 1   my ($chunk_id, $chunk_message_type_id) = @_;
936 0 0         return unless $chunk_id =~ /^[1-9][\d]*$/;
937              
938 0 0         our @rtmp_msg_type_id unless defined @rtmp_msg_type_id;
939 0           $rtmp_msg_type_id[$chunk_id] = $chunk_message_type_id;
940 0           return 1;
941             }
942              
943             =head2 get_rtmp_chunk_msg_type_id($chunk_id)
944              
945             =cut
946              
947             sub get_rtmp_chunk_msg_type_id
948             {
949 0     0 1   my ($chunk_id) = @_;
950 0 0         return unless $chunk_id =~ /^[1-9][\d]*$/;
951 0 0         print 'get msg type id before set.' unless defined @rtmp_msg_type_id;
952 0           return $rtmp_msg_type_id[$chunk_id];
953             }
954              
955             =head2 set_rtmp_chunk_msg_stream_id($chunk_id, $chunk_message_stream_id)
956              
957             =cut
958              
959             sub set_rtmp_chunk_msg_stream_id
960             {
961 0     0 1   my ($chunk_id, $chunk_message_stream_id) = @_;
962 0 0         return unless $chunk_id =~ /^[1-9][\d]*$/;
963              
964 0 0         our @rtmp_msg_stream_id unless defined @rtmp_msg_stream_id;
965 0           $rtmp_msg_stream_id[$chunk_id] = $chunk_message_stream_id;
966 0           return 1;
967             }
968              
969             =head2 get_rtmp_chunk_msg_stream_id($chunk_id)
970              
971             =cut
972              
973             sub get_rtmp_chunk_msg_stream_id
974             {
975 0     0 1   my ($chunk_id) = @_;
976 0 0         return unless $chunk_id =~ /^[1-9][\d]*$/;
977 0 0         print 'get msg stream id before set.' unless defined @rtmp_msg_stream_id;
978 0           return $rtmp_msg_stream_id[$chunk_id];
979             }
980              
981             =head2 set_rtmp_client_chunk_size($new_chunk_size)
982              
983             =cut
984              
985             sub set_rtmp_client_chunk_size
986             {
987 0     0 1   my ($new_chunk_size) = @_;
988              
989             #The maximum chunk size can be 65536 bytes. The
990             #maintained independently for each direction.
991 0 0         return unless $new_chunk_size =~ /^[\d]{1,5}$/;
992 0 0         return if $new_chunk_size > 65536;
993 0           our $rtmp_client_chunk_size = $new_chunk_size;
994             }
995              
996             =head2 get_rtmp_client_chunk_size
997              
998             =cut
999              
1000             sub get_rtmp_client_chunk_size
1001             {
1002 0 0   0 1   if (defined $rtmp_client_chunk_size)
1003             {
1004 0           return $rtmp_client_chunk_size;
1005             }
1006             else
1007             {
1008 0           our $rtmp_client_chunk_size = 128;
1009 0           return $rtmp_client_chunk_size;
1010             }
1011             }
1012              
1013             =head2 reset_rtmp_msg($chunk_id)
1014              
1015             =cut
1016              
1017             sub reset_rtmp_msg
1018             {
1019 0 0   0 1   print "reset rtmp msg before set it.\n" unless defined @rtmp_msg;
1020 0           my ($chunk_id) = @_;
1021 0           $rtmp_msg[$chunk_id] = '';
1022             }
1023              
1024             =head2 put_rtmp_msg($chunk_id, $string)
1025              
1026             =cut
1027              
1028             sub put_rtmp_msg
1029             {
1030 0     0 1   my ($chunk_id, $string) = @_;
1031 0 0         our @rtmp_msg unless defined @rtmp_msg;
1032 0           $rtmp_msg[$chunk_id] .= $string;
1033             }
1034              
1035             =head2 get_rtmp_msg
1036              
1037             =cut
1038              
1039             sub get_rtmp_msg
1040             {
1041 0 0   0 1   print "get rtmp msg before set it.\n" unless defined @rtmp_msg;
1042 0           my ($chunk_id) = @_;
1043 0           return $rtmp_msg[$chunk_id];
1044             }
1045              
1046             =head2 set_rtmp_window($window_size, $limit_type)
1047              
1048             =cut
1049              
1050             sub set_rtmp_window
1051             {
1052 0     0 1   my ($window_size, $limit_type) = @_;
1053 0           our $rtmp_my_window_size = $window_size;
1054             }
1055              
1056             =head2 get_rtmp_window()
1057              
1058             =cut
1059              
1060             sub get_rtmp_window
1061             {
1062 0 0   0 1   print "get rtmp window before set it.\n" unless defined $rtmp_my_window_size;
1063 0           return $rtmp_my_window_size;
1064             }
1065              
1066             =head2 set_rtmp_peer_window($window_size)
1067              
1068             =cut
1069              
1070             sub set_rtmp_peer_window
1071             {
1072 0     0 1   my ($window_size) = @_;
1073 0           our $rtmp_peer_window_size = $window_size;
1074             }
1075              
1076             =head2 get_rtmp_peer_window()
1077              
1078             =cut
1079              
1080             sub get_rtmp_peer_window
1081             {
1082 0 0   0 1   print "get rtmp window before set it.\n" unless defined $rtmp_peer_window_size;
1083 0           return $rtmp_peer_window_size;
1084             }
1085              
1086             =head2 dec($binary_data)
1087              
1088             Dump the data to dec
1089              
1090             =cut
1091              
1092             sub dec
1093             {
1094 0     0 1   return oct('0x'.unpack('H*', shift));
1095             }
1096              
1097             =head2 dec($string, $indent, $front_color, $back_color)
1098              
1099             print colorful strings
1100              
1101             =cut
1102              
1103             sub dprint
1104             {
1105 0     0 0   my ($string, $indent, $front_color, $back_color) = @_;
1106             #$front_color = 39 unless $front_color; # accept: 30-39
1107             #$back_color = 40 unless $back_color; # accept: 40-49
1108             #$color="\\033[$front_color;$back_color"."m";
1109             #$endstyle="\\033[0m";
1110             #$content = "\"".$color.$string.$endstyle."\"";
1111 0 0         print '-' x $indent, "> " if $debug_flag;
1112 0           print $string,"\n";
1113             #my $cmd = "echo $content\n";
1114             #system $cmd if $debug_flag;
1115             }
1116              
1117             =head2 analysis_rtmp_msg($msg_type, $msg)
1118              
1119             output the rtmp stream information to STDOUT
1120              
1121             =cut
1122              
1123             sub analysis_rtmp_msg
1124             {
1125 0     0 1   my ($msg_type, $msg) = @_;
1126 0 0         if ($msg)
1127             {
1128             #print '-' x 22, " msg content ", '-' x 22, "\n";
1129             #print "msg type: $msg_type\n";
1130 0 0         if ($msg_type == 20)
    0          
1131             {
1132 0           my $command_name_length = dec(substr($msg, 1, 2));
1133 0           my $command_name = substr($msg, 3, $command_name_length);
1134 0           dprint("command name: $command_name", 9);
1135             #print_hex($msg);
1136             }
1137             elsif ($msg_type == 22)
1138             {
1139 0           local $| = 1;
1140             #print_hex($msg);
1141             }
1142             }
1143             }
1144              
1145              
1146             =head1 AUTHOR
1147              
1148             Written by ChenGang, yikuyiku.com@gmail.com
1149             L
1150              
1151              
1152             =head1 COPYRIGHT
1153              
1154             Copyright (c) 2011 ChenGang.
1155              
1156             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
1157              
1158              
1159             =head1 SEE ALSO
1160              
1161             L
1162              
1163             =cut
1164              
1165             1;