正在显示
5 个修改的文件
包含
155 行增加
和
4 行删除
| @@ -41,8 +41,13 @@ SrsCodecSample::~SrsCodecSample() | @@ -41,8 +41,13 @@ SrsCodecSample::~SrsCodecSample() | ||
| 41 | 41 | ||
| 42 | void SrsCodecSample::clear() | 42 | void SrsCodecSample::clear() |
| 43 | { | 43 | { |
| 44 | - cts = 0; | 44 | + is_video = false; |
| 45 | nb_buffers = 0; | 45 | nb_buffers = 0; |
| 46 | + | ||
| 47 | + cts = 0; | ||
| 48 | + frame_type = SrsCodecVideoAVCFrameReserved; | ||
| 49 | + codec_id = SrsCodecVideoReserved; | ||
| 50 | + avc_packet_type = SrsCodecVideoAVCTypeReserved; | ||
| 46 | } | 51 | } |
| 47 | 52 | ||
| 48 | int SrsCodecSample::add_sample(char* bytes, int size) | 53 | int SrsCodecSample::add_sample(char* bytes, int size) |
| @@ -99,6 +104,8 @@ int SrsCodec::audio_aac_demux(int8_t* data, int size, SrsCodecSample* sample) | @@ -99,6 +104,8 @@ int SrsCodec::audio_aac_demux(int8_t* data, int size, SrsCodecSample* sample) | ||
| 99 | { | 104 | { |
| 100 | int ret = ERROR_SUCCESS; | 105 | int ret = ERROR_SUCCESS; |
| 101 | 106 | ||
| 107 | + sample->is_video = false; | ||
| 108 | + | ||
| 102 | if (!data || size <= 0) { | 109 | if (!data || size <= 0) { |
| 103 | srs_trace("no audio present, hls ignore it."); | 110 | srs_trace("no audio present, hls ignore it."); |
| 104 | return ret; | 111 | return ret; |
| @@ -176,6 +183,8 @@ int SrsCodec::video_avc_demux(int8_t* data, int size, SrsCodecSample* sample) | @@ -176,6 +183,8 @@ int SrsCodec::video_avc_demux(int8_t* data, int size, SrsCodecSample* sample) | ||
| 176 | { | 183 | { |
| 177 | int ret = ERROR_SUCCESS; | 184 | int ret = ERROR_SUCCESS; |
| 178 | 185 | ||
| 186 | + sample->is_video = true; | ||
| 187 | + | ||
| 179 | if (!data || size <= 0) { | 188 | if (!data || size <= 0) { |
| 180 | srs_trace("no video present, hls ignore it."); | 189 | srs_trace("no video present, hls ignore it."); |
| 181 | return ret; | 190 | return ret; |
| @@ -196,13 +205,16 @@ int SrsCodec::video_avc_demux(int8_t* data, int size, SrsCodecSample* sample) | @@ -196,13 +205,16 @@ int SrsCodec::video_avc_demux(int8_t* data, int size, SrsCodecSample* sample) | ||
| 196 | int8_t codec_id = frame_type & 0x0f; | 205 | int8_t codec_id = frame_type & 0x0f; |
| 197 | frame_type = (frame_type >> 4) & 0x0f; | 206 | frame_type = (frame_type >> 4) & 0x0f; |
| 198 | 207 | ||
| 199 | - video_codec_id = codec_id; | 208 | + sample->frame_type = (SrsCodecVideoAVCFrame)frame_type; |
| 209 | + sample->codec_id = (SrsCodecVideo)codec_id; | ||
| 210 | + | ||
| 200 | // only support h.264/avc | 211 | // only support h.264/avc |
| 201 | if (codec_id != SrsCodecVideoAVC) { | 212 | if (codec_id != SrsCodecVideoAVC) { |
| 202 | ret = ERROR_HLS_DECODE_ERROR; | 213 | ret = ERROR_HLS_DECODE_ERROR; |
| 203 | srs_error("hls only support video h.264/avc codec. ret=%d", ret); | 214 | srs_error("hls only support video h.264/avc codec. ret=%d", ret); |
| 204 | return ret; | 215 | return ret; |
| 205 | } | 216 | } |
| 217 | + video_codec_id = codec_id; | ||
| 206 | 218 | ||
| 207 | if (!stream->require(4)) { | 219 | if (!stream->require(4)) { |
| 208 | ret = ERROR_HLS_DECODE_ERROR; | 220 | ret = ERROR_HLS_DECODE_ERROR; |
| @@ -214,6 +226,7 @@ int SrsCodec::video_avc_demux(int8_t* data, int size, SrsCodecSample* sample) | @@ -214,6 +226,7 @@ int SrsCodec::video_avc_demux(int8_t* data, int size, SrsCodecSample* sample) | ||
| 214 | 226 | ||
| 215 | // pts = dts + cts. | 227 | // pts = dts + cts. |
| 216 | sample->cts = composition_time; | 228 | sample->cts = composition_time; |
| 229 | + sample->avc_packet_type = (SrsCodecVideoAVCType)avc_packet_type; | ||
| 217 | 230 | ||
| 218 | if (avc_packet_type == SrsCodecVideoAVCTypeSequenceHeader) { | 231 | if (avc_packet_type == SrsCodecVideoAVCTypeSequenceHeader) { |
| 219 | // AVCDecoderConfigurationRecord | 232 | // AVCDecoderConfigurationRecord |
| @@ -45,6 +45,8 @@ class SrsStream; | @@ -45,6 +45,8 @@ class SrsStream; | ||
| 45 | // 7 = AVC | 45 | // 7 = AVC |
| 46 | enum SrsCodecVideo | 46 | enum SrsCodecVideo |
| 47 | { | 47 | { |
| 48 | + SrsCodecVideoReserved = 0, | ||
| 49 | + | ||
| 48 | SrsCodecVideoSorensonH263 = 2, | 50 | SrsCodecVideoSorensonH263 = 2, |
| 49 | SrsCodecVideoScreenVideo = 3, | 51 | SrsCodecVideoScreenVideo = 3, |
| 50 | SrsCodecVideoOn2VP6 = 4, | 52 | SrsCodecVideoOn2VP6 = 4, |
| @@ -63,6 +65,8 @@ enum SrsCodecVideo | @@ -63,6 +65,8 @@ enum SrsCodecVideo | ||
| 63 | // 5 = video info/command frame | 65 | // 5 = video info/command frame |
| 64 | enum SrsCodecVideoAVCFrame | 66 | enum SrsCodecVideoAVCFrame |
| 65 | { | 67 | { |
| 68 | + SrsCodecVideoAVCFrameReserved = 0, | ||
| 69 | + | ||
| 66 | SrsCodecVideoAVCFrameKeyFrame = 1, | 70 | SrsCodecVideoAVCFrameKeyFrame = 1, |
| 67 | SrsCodecVideoAVCFrameInterFrame = 2, | 71 | SrsCodecVideoAVCFrameInterFrame = 2, |
| 68 | SrsCodecVideoAVCFrameDisposableInterFrame = 3, | 72 | SrsCodecVideoAVCFrameDisposableInterFrame = 3, |
| @@ -78,6 +82,8 @@ enum SrsCodecVideoAVCFrame | @@ -78,6 +82,8 @@ enum SrsCodecVideoAVCFrame | ||
| 78 | // not required or supported) | 82 | // not required or supported) |
| 79 | enum SrsCodecVideoAVCType | 83 | enum SrsCodecVideoAVCType |
| 80 | { | 84 | { |
| 85 | + SrsCodecVideoAVCTypeReserved = -1, | ||
| 86 | + | ||
| 81 | SrsCodecVideoAVCTypeSequenceHeader = 0, | 87 | SrsCodecVideoAVCTypeSequenceHeader = 0, |
| 82 | SrsCodecVideoAVCTypeNALU = 1, | 88 | SrsCodecVideoAVCTypeNALU = 1, |
| 83 | SrsCodecVideoAVCTypeSequenceHeaderEOF = 2, | 89 | SrsCodecVideoAVCTypeSequenceHeaderEOF = 2, |
| @@ -180,9 +186,14 @@ class SrsCodecSample | @@ -180,9 +186,14 @@ class SrsCodecSample | ||
| 180 | public: | 186 | public: |
| 181 | int nb_buffers; | 187 | int nb_buffers; |
| 182 | SrsCodecBuffer buffers[SRS_MAX_CODEC_SAMPLE]; | 188 | SrsCodecBuffer buffers[SRS_MAX_CODEC_SAMPLE]; |
| 189 | +public: | ||
| 190 | + bool is_video; | ||
| 183 | // CompositionTime, video_file_format_spec_v10_1.pdf, page 78. | 191 | // CompositionTime, video_file_format_spec_v10_1.pdf, page 78. |
| 184 | // cts = pts - dts, where dts = flvheader->timestamp. | 192 | // cts = pts - dts, where dts = flvheader->timestamp. |
| 185 | int32_t cts; | 193 | int32_t cts; |
| 194 | + SrsCodecVideoAVCFrame frame_type; | ||
| 195 | + SrsCodecVideo codec_id; | ||
| 196 | + SrsCodecVideoAVCType avc_packet_type; | ||
| 186 | public: | 197 | public: |
| 187 | SrsCodecSample(); | 198 | SrsCodecSample(); |
| 188 | virtual ~SrsCodecSample(); | 199 | virtual ~SrsCodecSample(); |
| @@ -110,5 +110,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | @@ -110,5 +110,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| 110 | #define ERROR_HLS_METADATA 600 | 110 | #define ERROR_HLS_METADATA 600 |
| 111 | #define ERROR_HLS_DECODE_ERROR 601 | 111 | #define ERROR_HLS_DECODE_ERROR 601 |
| 112 | #define ERROR_HLS_BUSY 602 | 112 | #define ERROR_HLS_BUSY 602 |
| 113 | +#define ERROR_HLS_OPEN_FAILED 603 | ||
| 114 | +#define ERROR_HLS_WRITE_FAILED 604 | ||
| 113 | 115 | ||
| 114 | #endif | 116 | #endif |
| @@ -23,6 +23,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | @@ -23,6 +23,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| 23 | 23 | ||
| 24 | #include <srs_core_hls.hpp> | 24 | #include <srs_core_hls.hpp> |
| 25 | 25 | ||
| 26 | +#include <sys/types.h> | ||
| 27 | +#include <sys/stat.h> | ||
| 28 | +#include <fcntl.h> | ||
| 29 | + | ||
| 26 | #include <srs_core_error.hpp> | 30 | #include <srs_core_error.hpp> |
| 27 | #include <srs_core_codec.hpp> | 31 | #include <srs_core_codec.hpp> |
| 28 | #include <srs_core_amf0.hpp> | 32 | #include <srs_core_amf0.hpp> |
| @@ -63,6 +67,9 @@ int SrsHLS::on_publish(std::string _vhost) | @@ -63,6 +67,9 @@ int SrsHLS::on_publish(std::string _vhost) | ||
| 63 | if (!conf && conf->arg0() == "off") { | 67 | if (!conf && conf->arg0() == "off") { |
| 64 | return ret; | 68 | return ret; |
| 65 | } | 69 | } |
| 70 | + | ||
| 71 | + // TODO: check the audio and video, ensure both exsists. | ||
| 72 | + // for use fixed mpegts header specifeid the audio and video pid. | ||
| 66 | 73 | ||
| 67 | hls_enabled = true; | 74 | hls_enabled = true; |
| 68 | 75 | ||
| @@ -71,6 +78,9 @@ int SrsHLS::on_publish(std::string _vhost) | @@ -71,6 +78,9 @@ int SrsHLS::on_publish(std::string _vhost) | ||
| 71 | path = conf->arg0(); | 78 | path = conf->arg0(); |
| 72 | } | 79 | } |
| 73 | 80 | ||
| 81 | + // TODO: generate by m3u8 muxer. | ||
| 82 | + path += "/1.ts"; | ||
| 83 | + | ||
| 74 | if ((ret = muxer->open(path)) != ERROR_SUCCESS) { | 84 | if ((ret = muxer->open(path)) != ERROR_SUCCESS) { |
| 75 | srs_error("open hls muxer failed. ret=%d", ret); | 85 | srs_error("open hls muxer failed. ret=%d", ret); |
| 76 | return ret; | 86 | return ret; |
| @@ -82,6 +92,7 @@ int SrsHLS::on_publish(std::string _vhost) | @@ -82,6 +92,7 @@ int SrsHLS::on_publish(std::string _vhost) | ||
| 82 | void SrsHLS::on_unpublish() | 92 | void SrsHLS::on_unpublish() |
| 83 | { | 93 | { |
| 84 | hls_enabled = false; | 94 | hls_enabled = false; |
| 95 | + muxer->close(); | ||
| 85 | srs_freep(muxer); | 96 | srs_freep(muxer); |
| 86 | } | 97 | } |
| 87 | 98 | ||
| @@ -185,6 +196,10 @@ int SrsHLS::on_audio(SrsCommonMessage* audio) | @@ -185,6 +196,10 @@ int SrsHLS::on_audio(SrsCommonMessage* audio) | ||
| 185 | return ret; | 196 | return ret; |
| 186 | } | 197 | } |
| 187 | 198 | ||
| 199 | + if ((ret = muxer->write(codec, sample)) != ERROR_SUCCESS) { | ||
| 200 | + return ret; | ||
| 201 | + } | ||
| 202 | + | ||
| 188 | return ret; | 203 | return ret; |
| 189 | } | 204 | } |
| 190 | 205 | ||
| @@ -206,20 +221,125 @@ int SrsHLS::on_video(SrsCommonMessage* video) | @@ -206,20 +221,125 @@ int SrsHLS::on_video(SrsCommonMessage* video) | ||
| 206 | return ret; | 221 | return ret; |
| 207 | } | 222 | } |
| 208 | 223 | ||
| 224 | + if ((ret = muxer->write(codec, sample)) != ERROR_SUCCESS) { | ||
| 225 | + return ret; | ||
| 226 | + } | ||
| 227 | + | ||
| 209 | return ret; | 228 | return ret; |
| 210 | } | 229 | } |
| 211 | 230 | ||
| 231 | +// @see: ngx_rtmp_mpegts_header | ||
| 232 | +static u_char mpegts_header[] = { | ||
| 233 | + /* TS */ | ||
| 234 | + 0x47, 0x40, 0x00, 0x10, 0x00, | ||
| 235 | + /* PSI */ | ||
| 236 | + 0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00, | ||
| 237 | + /* PAT */ | ||
| 238 | + 0x00, 0x01, 0xf0, 0x01, | ||
| 239 | + /* CRC */ | ||
| 240 | + 0x2e, 0x70, 0x19, 0x05, | ||
| 241 | + /* stuffing 167 bytes */ | ||
| 242 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 243 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 244 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 245 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 246 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 247 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 248 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 249 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 250 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 251 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 252 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 253 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 254 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 255 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 256 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 257 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 258 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 259 | + | ||
| 260 | + /* TS */ | ||
| 261 | + 0x47, 0x50, 0x01, 0x10, 0x00, | ||
| 262 | + /* PSI */ | ||
| 263 | + 0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00, | ||
| 264 | + /* PMT */ | ||
| 265 | + 0xe1, 0x00, | ||
| 266 | + 0xf0, 0x00, | ||
| 267 | + 0x1b, 0xe1, 0x00, 0xf0, 0x00, /* h264, pid=0x100=256 */ | ||
| 268 | + 0x0f, 0xe1, 0x01, 0xf0, 0x00, /* aac, pid=0x101=257 */ | ||
| 269 | + /*0x03, 0xe1, 0x01, 0xf0, 0x00,*/ /* mp3 */ | ||
| 270 | + /* CRC */ | ||
| 271 | + 0x2f, 0x44, 0xb9, 0x9b, /* crc for aac */ | ||
| 272 | + /*0x4e, 0x59, 0x3d, 0x1e,*/ /* crc for mp3 */ | ||
| 273 | + /* stuffing 157 bytes */ | ||
| 274 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 275 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 276 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 277 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 278 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 279 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 280 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 281 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 282 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 283 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 284 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 285 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 286 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 287 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 288 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||
| 289 | + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff | ||
| 290 | +}; | ||
| 291 | + | ||
| 292 | +// the mpegts header specifed the video/audio pid. | ||
| 293 | +#define TS_VIDEO_PID 256 | ||
| 294 | +#define TS_AUDIO_PID 257 | ||
| 295 | + | ||
| 212 | SrsTSMuxer::SrsTSMuxer() | 296 | SrsTSMuxer::SrsTSMuxer() |
| 213 | { | 297 | { |
| 298 | + fd = -1; | ||
| 214 | } | 299 | } |
| 215 | 300 | ||
| 216 | SrsTSMuxer::~SrsTSMuxer() | 301 | SrsTSMuxer::~SrsTSMuxer() |
| 217 | { | 302 | { |
| 303 | + close(); | ||
| 218 | } | 304 | } |
| 219 | 305 | ||
| 220 | -int SrsTSMuxer::open(std::string path) | 306 | +int SrsTSMuxer::open(std::string _path) |
| 307 | +{ | ||
| 308 | + int ret = ERROR_SUCCESS; | ||
| 309 | + | ||
| 310 | + path = _path; | ||
| 311 | + | ||
| 312 | + close(); | ||
| 313 | + | ||
| 314 | + int flags = O_CREAT|O_WRONLY|O_TRUNC; | ||
| 315 | + mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH; | ||
| 316 | + if ((fd = ::open(path.c_str(), flags, mode)) < 0) { | ||
| 317 | + ret = ERROR_HLS_OPEN_FAILED; | ||
| 318 | + srs_error("open ts file %s failed. ret=%d", path.c_str(), ret); | ||
| 319 | + return ret; | ||
| 320 | + } | ||
| 321 | + | ||
| 322 | + // write mpegts header | ||
| 323 | + if (::write(fd, mpegts_header, sizeof(mpegts_header)) != sizeof(mpegts_header)) { | ||
| 324 | + ret = ERROR_HLS_WRITE_FAILED; | ||
| 325 | + srs_error("write ts file header %s failed. ret=%d", path.c_str(), ret); | ||
| 326 | + return ret; | ||
| 327 | + } | ||
| 328 | + | ||
| 329 | + return ret; | ||
| 330 | +} | ||
| 331 | + | ||
| 332 | +int SrsTSMuxer::write(SrsCodec* codec, SrsCodecSample* sample) | ||
| 221 | { | 333 | { |
| 222 | int ret = ERROR_SUCCESS; | 334 | int ret = ERROR_SUCCESS; |
| 223 | return ret; | 335 | return ret; |
| 224 | } | 336 | } |
| 225 | 337 | ||
| 338 | +void SrsTSMuxer::close() | ||
| 339 | +{ | ||
| 340 | + if (fd > 0) { | ||
| 341 | + ::close(fd); | ||
| 342 | + fd = -1; | ||
| 343 | + } | ||
| 344 | +} | ||
| 345 | + |
| @@ -58,11 +58,16 @@ public: | @@ -58,11 +58,16 @@ public: | ||
| 58 | 58 | ||
| 59 | class SrsTSMuxer | 59 | class SrsTSMuxer |
| 60 | { | 60 | { |
| 61 | +private: | ||
| 62 | + int fd; | ||
| 63 | + std::string path; | ||
| 61 | public: | 64 | public: |
| 62 | SrsTSMuxer(); | 65 | SrsTSMuxer(); |
| 63 | virtual ~SrsTSMuxer(); | 66 | virtual ~SrsTSMuxer(); |
| 64 | public: | 67 | public: |
| 65 | - virtual int open(std::string path); | 68 | + virtual int open(std::string _path); |
| 69 | + virtual int write(SrsCodec* codec, SrsCodecSample* sample); | ||
| 70 | + virtual void close(); | ||
| 66 | }; | 71 | }; |
| 67 | 72 | ||
| 68 | #endif | 73 | #endif |
-
请 注册 或 登录 后发表评论