winlin

support flv parser, add amf0 to librtmp. 0.9.110

@@ -78,18 +78,6 @@ int main(int argc, char** argv) @@ -78,18 +78,6 @@ int main(int argc, char** argv)
78 return ret; 78 return ret;
79 } 79 }
80 80
81 -int parse_audio_data(char* data, int size)  
82 -{  
83 - int ret = 0;  
84 - return ret;  
85 -}  
86 -  
87 -int parse_video_data(char* data, int size)  
88 -{  
89 - int ret = 0;  
90 - return ret;  
91 -}  
92 -  
93 void digit_to_char(char* src, int ssize, char* dst, int dsize) 81 void digit_to_char(char* src, int ssize, char* dst, int dsize)
94 { 82 {
95 int i, j; 83 int i, j;
@@ -129,30 +117,93 @@ int parse_bytes(char* data, int size, char* hbuf, int hsize, char* tbuf, int tsi @@ -129,30 +117,93 @@ int parse_bytes(char* data, int size, char* hbuf, int hsize, char* tbuf, int tsi
129 { 117 {
130 memset(hbuf, 0, hsize); 118 memset(hbuf, 0, hsize);
131 memset(tbuf, 0, tsize); 119 memset(tbuf, 0, tsize);
132 - if (size > print_size * 2) { 120 +
  121 + if (size > 0) {
133 digit_to_char(data, size, hbuf, hsize - 1); 122 digit_to_char(data, size, hbuf, hsize - 1);
  123 + }
  124 +
  125 + if (size > print_size * 2) {
134 digit_to_char(data + size - print_size, size, tbuf, tsize - 1); 126 digit_to_char(data + size - print_size, size, tbuf, tsize - 1);
135 } 127 }
136 } 128 }
137 129
138 -int parse_script_data(char* data, int size) 130 +#define FLV_HEADER_SIZE 11
  131 +int parse_script_data(u_int32_t timestamp, char* data, int size, int64_t offset)
139 { 132 {
140 int ret = 0; 133 int ret = 0;
141 134
142 char hbuf[48]; 135 char hbuf[48];
143 char tbuf[48]; 136 char tbuf[48];
  137 +
  138 + int amf0_size = 0;
  139 + int nparsed = 0;
  140 +
  141 + srs_amf0_t amf0_name;
  142 + char* amf0_name_str = NULL;
  143 +
  144 + srs_amf0_t amf0_data;
  145 + char* amf0_data_str = NULL;
  146 +
  147 + // bytes
144 parse_bytes(data, size, hbuf, sizeof(hbuf), tbuf, sizeof(tbuf), 16); 148 parse_bytes(data, size, hbuf, sizeof(hbuf), tbuf, sizeof(tbuf), 16);
145 149
146 - srs_amf0_t amf0 = srs_amf0_parse(data, size);  
147 - if (amf0 == NULL) {  
148 - trace("invalid amf0 data."); 150 + // amf0
  151 + amf0_name = srs_amf0_parse(data, size, &nparsed);
  152 + if (amf0_name == NULL || nparsed >= size) {
  153 + trace("invalid amf0 name data.");
149 return -1; 154 return -1;
150 } 155 }
  156 + amf0_data = srs_amf0_parse(data + nparsed, size - nparsed, &nparsed);
  157 +
  158 + trace("packet type=%s, time=%d, size=%d, data-size=%d, \n"
  159 + "offset=%d\n[+00, +15] %s\n[-15, EOF] %s\n%s%s",
  160 + srs_type2string(SRS_RTMP_TYPE_SCRIPT), timestamp, size + FLV_HEADER_SIZE, size,
  161 + (int)offset, hbuf, tbuf,
  162 + srs_amf0_human_print(amf0_name, &amf0_name_str, &amf0_size),
  163 + srs_amf0_human_print(amf0_data, &amf0_data_str, &amf0_size));
  164 +
  165 + srs_amf0_free(amf0_name);
  166 + srs_amf0_free_bytes(amf0_name_str);
  167 +
  168 + srs_amf0_free(amf0_data);
  169 + srs_amf0_free_bytes(amf0_data_str);
  170 +
  171 + return ret;
  172 +}
  173 +
  174 +int parse_audio_data(u_int32_t timestamp, char* data, int size, int64_t offset)
  175 +{
  176 + int ret = 0;
  177 +
  178 + char hbuf[48];
  179 + char tbuf[48];
  180 +
  181 + // bytes
  182 + parse_bytes(data, size, hbuf, sizeof(hbuf), tbuf, sizeof(tbuf), 16);
  183 +
  184 + trace("packet type=%s, time=%d, size=%d, data-size=%d, \n"
  185 + "offset=%d\n[+00, +15] %s\n[-15, EOF] %s\n",
  186 + srs_type2string(SRS_RTMP_TYPE_AUDIO), timestamp, size + FLV_HEADER_SIZE, size,
  187 + (int)offset, hbuf, tbuf);
151 188
152 - trace("details:\n"  
153 - "[+00, +15] %s\n[-15, EOF] %s",  
154 - hbuf, tbuf); 189 + return ret;
  190 +}
155 191
  192 +int parse_video_data(u_int32_t timestamp, char* data, int size, int64_t offset)
  193 +{
  194 + int ret = 0;
  195 +
  196 + char hbuf[48];
  197 + char tbuf[48];
  198 +
  199 + // bytes
  200 + parse_bytes(data, size, hbuf, sizeof(hbuf), tbuf, sizeof(tbuf), 16);
  201 +
  202 + trace("packet type=%s, time=%d, size=%d, data-size=%d, \n"
  203 + "offset=%d\n[+00, +15] %s\n[-15, EOF] %s\n",
  204 + srs_type2string(SRS_RTMP_TYPE_VIDEO), timestamp, size + FLV_HEADER_SIZE, size,
  205 + (int)offset, hbuf, tbuf);
  206 +
156 return ret; 207 return ret;
157 } 208 }
158 209
@@ -168,9 +219,12 @@ int parse_flv(int flv_fd) @@ -168,9 +219,12 @@ int parse_flv(int flv_fd)
168 int type, size; 219 int type, size;
169 u_int32_t timestamp = 0; 220 u_int32_t timestamp = 0;
170 char* data = NULL; 221 char* data = NULL;
  222 + int64_t offset = 0;
171 223
172 trace("start parse flv"); 224 trace("start parse flv");
173 for (;;) { 225 for (;;) {
  226 + offset = lseek(flv_fd, 0, SEEK_CUR);
  227 +
174 if ((ret = flv_read_packet(flv_fd, &type, &timestamp, &data, &size)) != 0) { 228 if ((ret = flv_read_packet(flv_fd, &type, &timestamp, &data, &size)) != 0) {
175 if (ret == ERROR_FLV_CODEC_EOF) { 229 if (ret == ERROR_FLV_CODEC_EOF) {
176 trace("parse completed."); 230 trace("parse completed.");
@@ -179,19 +233,18 @@ int parse_flv(int flv_fd) @@ -179,19 +233,18 @@ int parse_flv(int flv_fd)
179 trace("irtmp get packet failed. ret=%d", ret); 233 trace("irtmp get packet failed. ret=%d", ret);
180 return ret; 234 return ret;
181 } 235 }
182 - trace("flv got packet: type=%s, time=%d, size=%d", srs_type2string(type), timestamp, size);  
183 236
184 // data tag 237 // data tag
185 if (type == SRS_RTMP_TYPE_AUDIO) { 238 if (type == SRS_RTMP_TYPE_AUDIO) {
186 - if ((ret = parse_audio_data(data, size)) != 0) { 239 + if ((ret = parse_audio_data(timestamp, data, size, offset)) != 0) {
187 return ret; 240 return ret;
188 } 241 }
189 } else if (type == SRS_RTMP_TYPE_VIDEO) { 242 } else if (type == SRS_RTMP_TYPE_VIDEO) {
190 - if ((ret = parse_video_data(data, size)) != 0) { 243 + if ((ret = parse_video_data(timestamp, data, size, offset)) != 0) {
191 return ret; 244 return ret;
192 } 245 }
193 } else { 246 } else {
194 - if ((ret = parse_script_data(data, size)) != 0) { 247 + if ((ret = parse_script_data(timestamp, data, size, offset)) != 0) {
195 return ret; 248 return ret;
196 } 249 }
197 } 250 }
@@ -80,6 +80,7 @@ int SrsConnection::cycle() @@ -80,6 +80,7 @@ int SrsConnection::cycle()
80 80
81 void SrsConnection::on_thread_stop() 81 void SrsConnection::on_thread_stop()
82 { 82 {
  83 + // TODO: FIXME: never remove itself, use isolate thread to do cleanup.
83 server->remove(this); 84 server->remove(this);
84 } 85 }
85 86
@@ -31,7 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @@ -31,7 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 // current release version 31 // current release version
32 #define VERSION_MAJOR "0" 32 #define VERSION_MAJOR "0"
33 #define VERSION_MINOR "9" 33 #define VERSION_MINOR "9"
34 -#define VERSION_REVISION "109" 34 +#define VERSION_REVISION "110"
35 #define RTMP_SIG_SRS_VERSION VERSION_MAJOR"."VERSION_MINOR"."VERSION_REVISION 35 #define RTMP_SIG_SRS_VERSION VERSION_MAJOR"."VERSION_MINOR"."VERSION_REVISION
36 // server info. 36 // server info.
37 #define RTMP_SIG_SRS_KEY "SRS" 37 #define RTMP_SIG_SRS_KEY "SRS"
@@ -26,6 +26,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @@ -26,6 +26,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 #include <stdlib.h> 26 #include <stdlib.h>
27 27
28 #include <string> 28 #include <string>
  29 +#include <sstream>
29 using namespace std; 30 using namespace std;
30 31
31 #include <srs_kernel_error.hpp> 32 #include <srs_kernel_error.hpp>
@@ -408,7 +409,7 @@ int64_t srs_get_time_ms() @@ -408,7 +409,7 @@ int64_t srs_get_time_ms()
408 return srs_get_system_time_ms(); 409 return srs_get_system_time_ms();
409 } 410 }
410 411
411 -srs_amf0_t srs_amf0_parse(char* data, int size) 412 +srs_amf0_t srs_amf0_parse(char* data, int size, int* nparsed)
412 { 413 {
413 int ret = ERROR_SUCCESS; 414 int ret = ERROR_SUCCESS;
414 415
@@ -430,11 +431,23 @@ srs_amf0_t srs_amf0_parse(char* data, int size) @@ -430,11 +431,23 @@ srs_amf0_t srs_amf0_parse(char* data, int size)
430 return amf0; 431 return amf0;
431 } 432 }
432 433
  434 + *nparsed = stream.pos();
433 amf0 = (srs_amf0_t)any; 435 amf0 = (srs_amf0_t)any;
434 436
435 return amf0; 437 return amf0;
436 } 438 }
437 439
  440 +void srs_amf0_free(srs_amf0_t amf0)
  441 +{
  442 + SrsAmf0Any* any = (SrsAmf0Any*)amf0;
  443 + srs_freep(any);
  444 +}
  445 +
  446 +void srs_amf0_free_bytes(char* data)
  447 +{
  448 + srs_freep(data);
  449 +}
  450 +
438 amf0_bool srs_amf0_is_string(srs_amf0_t amf0) 451 amf0_bool srs_amf0_is_string(srs_amf0_t amf0)
439 { 452 {
440 SrsAmf0Any* any = (SrsAmf0Any*)amf0; 453 SrsAmf0Any* any = (SrsAmf0Any*)amf0;
@@ -471,6 +484,130 @@ amf0_bool srs_amf0_is_ecma_array(srs_amf0_t amf0) @@ -471,6 +484,130 @@ amf0_bool srs_amf0_is_ecma_array(srs_amf0_t amf0)
471 return any->is_ecma_array(); 484 return any->is_ecma_array();
472 } 485 }
473 486
  487 +const char* srs_amf0_to_string(srs_amf0_t amf0)
  488 +{
  489 + SrsAmf0Any* any = (SrsAmf0Any*)amf0;
  490 + return any->to_str_raw();
  491 +}
  492 +
  493 +amf0_bool srs_amf0_to_boolean(srs_amf0_t amf0)
  494 +{
  495 + SrsAmf0Any* any = (SrsAmf0Any*)amf0;
  496 + return any->to_boolean();
  497 +}
  498 +
  499 +amf0_number srs_amf0_to_number(srs_amf0_t amf0)
  500 +{
  501 + SrsAmf0Any* any = (SrsAmf0Any*)amf0;
  502 + return any->to_number();
  503 +}
  504 +
  505 +int srs_amf0_object_property_count(srs_amf0_t amf0)
  506 +{
  507 + SrsAmf0Object* obj = (SrsAmf0Object*)amf0;
  508 + return obj->count();
  509 +}
  510 +
  511 +const char* srs_amf0_object_property_name_at(srs_amf0_t amf0, int index)
  512 +{
  513 + SrsAmf0Object* obj = (SrsAmf0Object*)amf0;
  514 + return obj->key_raw_at(index);
  515 +}
  516 +
  517 +srs_amf0_t srs_amf0_object_property_value_at(srs_amf0_t amf0, int index)
  518 +{
  519 + SrsAmf0Object* obj = (SrsAmf0Object*)amf0;
  520 + return (srs_amf0_t)obj->value_at(index);
  521 +}
  522 +
  523 +int srs_amf0_array_property_count(srs_amf0_t amf0)
  524 +{
  525 + SrsAmf0EcmaArray * obj = (SrsAmf0EcmaArray*)amf0;
  526 + return obj->count();
  527 +}
  528 +
  529 +const char* srs_amf0_array_property_name_at(srs_amf0_t amf0, int index)
  530 +{
  531 + SrsAmf0EcmaArray* obj = (SrsAmf0EcmaArray*)amf0;
  532 + return obj->key_raw_at(index);
  533 +}
  534 +
  535 +srs_amf0_t srs_amf0_array_property_value_at(srs_amf0_t amf0, int index)
  536 +{
  537 + SrsAmf0EcmaArray* obj = (SrsAmf0EcmaArray*)amf0;
  538 + return (srs_amf0_t)obj->value_at(index);
  539 +}
  540 +
  541 +void __srs_amf0_do_print(SrsAmf0Any* any, stringstream& ss, int& level)
  542 +{
  543 + if (true) {
  544 + for (int i = 0; i < level; i++) {
  545 + ss << " ";
  546 + }
  547 + }
  548 +
  549 + if (any->is_boolean()) {
  550 + ss << "Boolean " << (any->to_boolean()? "true":"false") << endl;
  551 + } else if (any->is_number()) {
  552 + ss << "Number " << std::fixed << any->to_number() << endl;
  553 + } else if (any->is_string()) {
  554 + ss << "String " << any->to_str() << endl;
  555 + } else if (any->is_null()) {
  556 + ss << "Null" << endl;
  557 + } else if (any->is_ecma_array()) {
  558 + SrsAmf0EcmaArray* obj = any->to_ecma_array();
  559 + ss << "EcmaArray " << "(" << obj->count() << " items)" << endl;
  560 + for (int i = 0; i < obj->count(); i++) {
  561 + ss << " Property '" << obj->key_at(i) << "' ";
  562 + if (obj->value_at(i)->is_object() || obj->value_at(i)->is_ecma_array()) {
  563 + int next_level = level + 1;
  564 + __srs_amf0_do_print(obj->value_at(i), ss, next_level);
  565 + } else {
  566 + int next_level = 0;
  567 + __srs_amf0_do_print(obj->value_at(i), ss, next_level);
  568 + }
  569 + }
  570 + } else if (any->is_object()) {
  571 + SrsAmf0Object* obj = any->to_object();
  572 + ss << "Object " << "(" << obj->count() << " items)" << endl;
  573 + for (int i = 0; i < obj->count(); i++) {
  574 + ss << " Property '" << obj->key_at(i) << "' ";
  575 + if (obj->value_at(i)->is_object() || obj->value_at(i)->is_ecma_array()) {
  576 + int next_level = level + 1;
  577 + __srs_amf0_do_print(obj->value_at(i), ss, next_level);
  578 + } else {
  579 + int next_level = 0;
  580 + __srs_amf0_do_print(obj->value_at(i), ss, next_level);
  581 + }
  582 + }
  583 + } else {
  584 + ss << "Unknown" << endl;
  585 + }
  586 +}
  587 +
  588 +char* srs_amf0_human_print(srs_amf0_t amf0, char** pdata, int* psize)
  589 +{
  590 + stringstream ss;
  591 +
  592 + ss.precision(1);
  593 +
  594 + SrsAmf0Any* any = (SrsAmf0Any*)amf0;
  595 +
  596 + int level = 0;
  597 + __srs_amf0_do_print(any, ss, level);
  598 +
  599 + string str = ss.str();
  600 + if (str.empty()) {
  601 + return NULL;
  602 + }
  603 +
  604 + *pdata = new char[str.length()];
  605 + *psize = str.length();
  606 + memcpy(*pdata, str.data(), str.length());
  607 +
  608 + return *pdata;
  609 +}
  610 +
474 #ifdef __cplusplus 611 #ifdef __cplusplus
475 } 612 }
476 #endif 613 #endif
@@ -165,18 +165,31 @@ int64_t srs_get_time_ms(); @@ -165,18 +165,31 @@ int64_t srs_get_time_ms();
165 /* the output handler. */ 165 /* the output handler. */
166 typedef void* srs_amf0_t; 166 typedef void* srs_amf0_t;
167 typedef int amf0_bool; 167 typedef int amf0_bool;
168 -extern srs_amf0_t srs_amf0_parse(char* data, int size); 168 +typedef double amf0_number;
  169 +srs_amf0_t srs_amf0_parse(char* data, int size, int* nparsed);
  170 +void srs_amf0_free(srs_amf0_t amf0);
  171 +void srs_amf0_free_bytes(char* data);
169 /* type detecter */ 172 /* type detecter */
170 -extern amf0_bool srs_amf0_is_string(srs_amf0_t amf0);  
171 -extern amf0_bool srs_amf0_is_boolean(srs_amf0_t amf0);  
172 -extern amf0_bool srs_amf0_is_number(srs_amf0_t amf0);  
173 -extern amf0_bool srs_amf0_is_null(srs_amf0_t amf0);  
174 -extern amf0_bool srs_amf0_is_object(srs_amf0_t amf0);  
175 -extern amf0_bool srs_amf0_is_ecma_array(srs_amf0_t amf0); 173 +amf0_bool srs_amf0_is_string(srs_amf0_t amf0);
  174 +amf0_bool srs_amf0_is_boolean(srs_amf0_t amf0);
  175 +amf0_bool srs_amf0_is_number(srs_amf0_t amf0);
  176 +amf0_bool srs_amf0_is_null(srs_amf0_t amf0);
  177 +amf0_bool srs_amf0_is_object(srs_amf0_t amf0);
  178 +amf0_bool srs_amf0_is_ecma_array(srs_amf0_t amf0);
176 /* value converter */ 179 /* value converter */
177 -/*const char* srs_amf0_to_string(srs_amf0_t amf0);  
178 -bool srs_amf0_to_boolean(srs_amf0_t amf0);  
179 -double srs_amf0_to_number(srs_amf0_t amf0);*/ 180 +const char* srs_amf0_to_string(srs_amf0_t amf0);
  181 +amf0_bool srs_amf0_to_boolean(srs_amf0_t amf0);
  182 +amf0_number srs_amf0_to_number(srs_amf0_t amf0);
  183 +/* object value converter */
  184 +int srs_amf0_object_property_count(srs_amf0_t amf0);
  185 +const char* srs_amf0_object_property_name_at(srs_amf0_t amf0, int index);
  186 +srs_amf0_t srs_amf0_object_property_value_at(srs_amf0_t amf0, int index);
  187 +/* array value converter */
  188 +int srs_amf0_array_property_count(srs_amf0_t amf0);
  189 +const char* srs_amf0_array_property_name_at(srs_amf0_t amf0, int index);
  190 +srs_amf0_t srs_amf0_array_property_value_at(srs_amf0_t amf0, int index);
  191 +/* human readable print */
  192 +char* srs_amf0_human_print(srs_amf0_t amf0, char** pdata, int* psize);
180 193
181 #ifdef __cplusplus 194 #ifdef __cplusplus
182 } 195 }
@@ -166,6 +166,7 @@ public: @@ -166,6 +166,7 @@ public:
166 virtual int count(); 166 virtual int count();
167 virtual void clear(); 167 virtual void clear();
168 virtual std::string key_at(int index); 168 virtual std::string key_at(int index);
  169 + virtual const char* key_raw_at(int index);
169 virtual SrsAmf0Any* value_at(int index); 170 virtual SrsAmf0Any* value_at(int index);
170 virtual void set(std::string key, SrsAmf0Any* value); 171 virtual void set(std::string key, SrsAmf0Any* value);
171 172
@@ -257,6 +258,13 @@ string SrsAmf0Any::to_str() @@ -257,6 +258,13 @@ string SrsAmf0Any::to_str()
257 return p->value; 258 return p->value;
258 } 259 }
259 260
  261 +const char* SrsAmf0Any::to_str_raw()
  262 +{
  263 + __SrsAmf0String* p = dynamic_cast<__SrsAmf0String*>(this);
  264 + srs_assert(p != NULL);
  265 + return p->value.data();
  266 +}
  267 +
260 bool SrsAmf0Any::to_boolean() 268 bool SrsAmf0Any::to_boolean()
261 { 269 {
262 __SrsAmf0Boolean* p = dynamic_cast<__SrsAmf0Boolean*>(this); 270 __SrsAmf0Boolean* p = dynamic_cast<__SrsAmf0Boolean*>(this);
@@ -425,6 +433,13 @@ string __SrsUnSortedHashtable::key_at(int index) @@ -425,6 +433,13 @@ string __SrsUnSortedHashtable::key_at(int index)
425 return elem.first; 433 return elem.first;
426 } 434 }
427 435
  436 +const char* __SrsUnSortedHashtable::key_raw_at(int index)
  437 +{
  438 + srs_assert(index < count());
  439 + SrsAmf0ObjectPropertyType& elem = properties[index];
  440 + return elem.first.data();
  441 +}
  442 +
428 SrsAmf0Any* __SrsUnSortedHashtable::value_at(int index) 443 SrsAmf0Any* __SrsUnSortedHashtable::value_at(int index)
429 { 444 {
430 srs_assert(index < count()); 445 srs_assert(index < count());
@@ -719,6 +734,11 @@ string SrsAmf0Object::key_at(int index) @@ -719,6 +734,11 @@ string SrsAmf0Object::key_at(int index)
719 return properties->key_at(index); 734 return properties->key_at(index);
720 } 735 }
721 736
  737 +const char* SrsAmf0Object::key_raw_at(int index)
  738 +{
  739 + return properties->key_raw_at(index);
  740 +}
  741 +
722 SrsAmf0Any* SrsAmf0Object::value_at(int index) 742 SrsAmf0Any* SrsAmf0Object::value_at(int index)
723 { 743 {
724 return properties->value_at(index); 744 return properties->value_at(index);
@@ -906,6 +926,11 @@ string SrsAmf0EcmaArray::key_at(int index) @@ -906,6 +926,11 @@ string SrsAmf0EcmaArray::key_at(int index)
906 return properties->key_at(index); 926 return properties->key_at(index);
907 } 927 }
908 928
  929 +const char* SrsAmf0EcmaArray::key_raw_at(int index)
  930 +{
  931 + return properties->key_raw_at(index);
  932 +}
  933 +
909 SrsAmf0Any* SrsAmf0EcmaArray::value_at(int index) 934 SrsAmf0Any* SrsAmf0EcmaArray::value_at(int index)
910 { 935 {
911 return properties->value_at(index); 936 return properties->value_at(index);
@@ -102,6 +102,7 @@ public: @@ -102,6 +102,7 @@ public:
102 * user must ensure the type is a string, or assert failed. 102 * user must ensure the type is a string, or assert failed.
103 */ 103 */
104 virtual std::string to_str(); 104 virtual std::string to_str();
  105 + virtual const char* to_str_raw();
105 /** 106 /**
106 * get the boolean of any when is_boolean() indicates true. 107 * get the boolean of any when is_boolean() indicates true.
107 * user must ensure the type is a boolean, or assert failed. 108 * user must ensure the type is a boolean, or assert failed.
@@ -172,6 +173,7 @@ public: @@ -172,6 +173,7 @@ public:
172 virtual int count(); 173 virtual int count();
173 // @remark: max index is count(). 174 // @remark: max index is count().
174 virtual std::string key_at(int index); 175 virtual std::string key_at(int index);
  176 + virtual const char* key_raw_at(int index);
175 // @remark: max index is count(). 177 // @remark: max index is count().
176 virtual SrsAmf0Any* value_at(int index); 178 virtual SrsAmf0Any* value_at(int index);
177 179
@@ -212,6 +214,7 @@ public: @@ -212,6 +214,7 @@ public:
212 virtual int count(); 214 virtual int count();
213 // @remark: max index is count(). 215 // @remark: max index is count().
214 virtual std::string key_at(int index); 216 virtual std::string key_at(int index);
  217 + virtual const char* key_raw_at(int index);
215 // @remark: max index is count(). 218 // @remark: max index is count().
216 virtual SrsAmf0Any* value_at(int index); 219 virtual SrsAmf0Any* value_at(int index);
217 220