00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "mythlogging.h"
00023 #include "mythcorecontext.h"
00024 #include "NuppelVideoRecorder.h"
00025 #include "avformatwriter.h"
00026
00027 extern "C" {
00028 #if HAVE_BIGENDIAN
00029 #include "byteswap.h"
00030 #endif
00031 #include "libavutil/opt.h"
00032 }
00033
00034 #define LOC QString("AVFW(%1): ").arg(m_filename)
00035 #define LOC_ERR QString("AVFW(%1) Error: ").arg(m_filename)
00036 #define LOC_WARN QString("AVFW(%1) Warning: ").arg(m_filename)
00037
00038 AVFormatWriter::AVFormatWriter()
00039 : FileWriterBase(),
00040
00041 m_avfRingBuffer(NULL), m_ringBuffer(NULL),
00042
00043 m_ctx(NULL),
00044 m_videoStream(NULL), m_avVideoCodec(NULL),
00045 m_audioStream(NULL), m_avAudioCodec(NULL),
00046 m_picture(NULL), m_tmpPicture(NULL),
00047 m_videoOutBuf(NULL), m_videoOutBufSize(0),
00048 m_audioSamples(NULL), m_audioOutBuf(NULL),
00049 m_audioOutBufSize(0), m_audioInputFrameSize(0)
00050 {
00051 av_register_all();
00052 avcodec_register_all();
00053
00054
00055
00056
00057 }
00058
00059 AVFormatWriter::~AVFormatWriter()
00060 {
00061 QMutexLocker locker(avcodeclock);
00062
00063 if (m_pkt)
00064 {
00065 av_free_packet(m_pkt);
00066 delete m_pkt;
00067 m_pkt = NULL;
00068 }
00069
00070 if (m_audPkt)
00071 {
00072 av_free_packet(m_audPkt);
00073 delete m_audPkt;
00074 m_audPkt = NULL;
00075 }
00076
00077 if (m_ctx)
00078 {
00079 av_write_trailer(m_ctx);
00080 avio_close(m_ctx->pb);
00081 for(unsigned int i = 0; i < m_ctx->nb_streams; i++) {
00082 av_freep(&m_ctx->streams[i]);
00083 }
00084
00085 av_free(m_ctx);
00086 m_ctx = NULL;
00087 }
00088
00089 if (m_videoOutBuf)
00090 delete [] m_videoOutBuf;
00091 }
00092
00093 bool AVFormatWriter::Init(void)
00094 {
00095 if (m_videoOutBuf)
00096 delete [] m_videoOutBuf;
00097
00098 if (m_width && m_height)
00099 m_videoOutBuf = new unsigned char[m_width * m_height * 2 + 10];
00100
00101 AVOutputFormat *fmt = av_guess_format(m_container.toAscii().constData(),
00102 NULL, NULL);
00103 if (!fmt)
00104 {
00105 LOG(VB_RECORD, LOG_ERR, LOC +
00106 QString("Init(): Unable to guess AVOutputFormat from container %1")
00107 .arg(m_container));
00108 return false;
00109 }
00110
00111 m_fmt = *fmt;
00112
00113 if (m_width && m_height)
00114 {
00115 m_avVideoCodec = avcodec_find_encoder_by_name(
00116 m_videoCodec.toAscii().constData());
00117 if (!m_avVideoCodec)
00118 {
00119 LOG(VB_RECORD, LOG_ERR, LOC +
00120 QString("Init(): Unable to find video codec %1").arg(m_videoCodec));
00121 return false;
00122 }
00123
00124 m_fmt.video_codec = m_avVideoCodec->id;
00125 }
00126 else
00127 m_fmt.video_codec = CODEC_ID_NONE;
00128
00129 m_avAudioCodec = avcodec_find_encoder_by_name(
00130 m_audioCodec.toAscii().constData());
00131 if (!m_avAudioCodec)
00132 {
00133 LOG(VB_RECORD, LOG_ERR, LOC +
00134 QString("Init(): Unable to find audio codec %1").arg(m_audioCodec));
00135 return false;
00136 }
00137
00138 m_fmt.audio_codec = m_avAudioCodec->id;
00139
00140 m_ctx = avformat_alloc_context();
00141 if (!m_ctx)
00142 {
00143 LOG(VB_RECORD, LOG_ERR,
00144 LOC + "Init(): Unable to allocate AVFormatContext");
00145 return false;
00146 }
00147
00148 m_ctx->oformat = &m_fmt;
00149
00150 if (m_container == "mpegts")
00151 m_ctx->packet_size = 2324;
00152
00153 snprintf(m_ctx->filename, sizeof(m_ctx->filename), "%s",
00154 m_filename.toAscii().constData());
00155
00156 if (m_fmt.video_codec != CODEC_ID_NONE)
00157 m_videoStream = AddVideoStream();
00158 if (m_fmt.audio_codec != CODEC_ID_NONE)
00159 m_audioStream = AddAudioStream();
00160
00161 m_pkt = new AVPacket;
00162 if (!m_pkt)
00163 {
00164 LOG(VB_RECORD, LOG_ERR, LOC + "Init(): error allocating AVPacket");
00165 return false;
00166 }
00167 av_new_packet(m_pkt, m_ctx->packet_size);
00168
00169 m_audPkt = new AVPacket;
00170 if (!m_audPkt)
00171 {
00172 LOG(VB_RECORD, LOG_ERR, LOC + "Init(): error allocating AVPacket");
00173 return false;
00174 }
00175 av_new_packet(m_audPkt, m_ctx->packet_size);
00176
00177 if ((m_videoStream) && (!OpenVideo()))
00178 {
00179 LOG(VB_RECORD, LOG_ERR, LOC + "Init(): OpenVideo() failed");
00180 return false;
00181 }
00182
00183 if ((m_audioStream) && (!OpenAudio()))
00184 {
00185 LOG(VB_RECORD, LOG_ERR, LOC + "Init(): OpenAudio() failed");
00186 return false;
00187 }
00188
00189 return true;
00190 }
00191
00192 bool AVFormatWriter::OpenFile(void)
00193 {
00194 if (!(m_fmt.flags & AVFMT_NOFILE))
00195 {
00196 if (avio_open(&m_ctx->pb, m_filename.toAscii().constData(),
00197 AVIO_FLAG_WRITE) < 0)
00198 {
00199 LOG(VB_RECORD, LOG_ERR, LOC + "OpenFile(): avio_open() failed");
00200 return false;
00201 }
00202 }
00203
00204 m_ringBuffer = RingBuffer::Create(m_filename, true);
00205
00206 if (!m_ringBuffer)
00207 {
00208 LOG(VB_RECORD, LOG_ERR, LOC +
00209 "OpenFile(): RingBuffer::Create() failed");
00210 return false;
00211 }
00212
00213 m_avfRingBuffer = new AVFRingBuffer(m_ringBuffer);
00214 URLContext *uc = (URLContext *)m_ctx->pb->opaque;
00215 uc->prot = &AVF_RingBuffer_Protocol;
00216 uc->priv_data = (void *)m_avfRingBuffer;
00217
00218 avformat_write_header(m_ctx, NULL);
00219
00220 return true;
00221 }
00222
00223 bool AVFormatWriter::CloseFile(void)
00224 {
00225 if (m_ctx)
00226 {
00227 av_write_trailer(m_ctx);
00228 avio_close(m_ctx->pb);
00229 for(unsigned int i = 0; i < m_ctx->nb_streams; i++) {
00230 av_freep(&m_ctx->streams[i]);
00231 }
00232
00233 av_free(m_ctx);
00234 m_ctx = NULL;
00235 }
00236
00237 return true;
00238 }
00239
00240 bool AVFormatWriter::NextFrameIsKeyFrame(void)
00241 {
00242 if ((m_framesWritten % m_keyFrameDist) == 0)
00243 return true;
00244
00245 return false;
00246 }
00247
00248 bool AVFormatWriter::WriteVideoFrame(VideoFrame *frame)
00249 {
00250 AVCodecContext *c;
00251
00252 c = m_videoStream->codec;
00253
00254 uint8_t *planes[3];
00255 int len = frame->size;
00256 unsigned char *buf = frame->buf;
00257
00258 planes[0] = buf;
00259 planes[1] = planes[0] + frame->width * frame->height;
00260 planes[2] = planes[1] + (frame->width * frame->height) /
00261 4;
00262
00263 m_picture->data[0] = planes[0];
00264 m_picture->data[1] = planes[1];
00265 m_picture->data[2] = planes[2];
00266 m_picture->linesize[0] = frame->width;
00267 m_picture->linesize[1] = frame->width / 2;
00268 m_picture->linesize[2] = frame->width / 2;
00269 m_picture->pts = m_framesWritten + 1;
00270 m_picture->type = FF_BUFFER_TYPE_SHARED;
00271
00272 if ((m_framesWritten % m_keyFrameDist) == 0)
00273 m_picture->pict_type = AV_PICTURE_TYPE_I;
00274 else
00275 m_picture->pict_type = AV_PICTURE_TYPE_NONE;
00276
00277 int got_pkt = 0;
00278 int ret = 0;
00279
00280 av_init_packet(m_pkt);
00281 m_pkt->data = (unsigned char *)m_videoOutBuf;
00282 m_pkt->size = len;
00283
00284 {
00285 QMutexLocker locker(avcodeclock);
00286 ret = avcodec_encode_video2(m_videoStream->codec, m_pkt,
00287 m_picture, &got_pkt);
00288 }
00289
00290 if (ret < 0 || !got_pkt)
00291 {
00292 #if 0
00293 LOG(VB_RECORD, LOG_ERR, QString("WriteVideoFrame(): cs: %1, mfw: %2, tc: %3, fn: %4").arg(m_pkt->size).arg(m_framesWritten).arg(frame->timecode).arg(frame->frameNumber));
00294 #endif
00295 return false;
00296 }
00297
00298 if ((m_framesWritten % m_keyFrameDist) == 0)
00299 m_pkt->flags |= AV_PKT_FLAG_KEY;
00300
00301 long long tc = frame->timecode;
00302 if (m_startingTimecodeOffset == -1)
00303 m_startingTimecodeOffset = tc;
00304 tc -= m_startingTimecodeOffset;
00305
00306 m_pkt->pts = tc * m_videoStream->time_base.den / m_videoStream->time_base.num / 1000;
00307 m_pkt->dts = AV_NOPTS_VALUE;
00308 m_pkt->stream_index= m_videoStream->index;
00309
00310
00311 ret = av_interleaved_write_frame(m_ctx, m_pkt);
00312 if (ret != 0)
00313 LOG(VB_RECORD, LOG_ERR, LOC + "WriteVideoFrame(): "
00314 "av_interleaved_write_frame couldn't write Video");
00315
00316 m_framesWritten++;
00317
00318 return true;
00319 }
00320
00321 #if HAVE_BIGENDIAN
00322 static void bswap_16_buf(short int *buf, int buf_cnt, int audio_channels)
00323 __attribute__ ((unused));
00324
00325 static void bswap_16_buf(short int *buf, int buf_cnt, int audio_channels)
00326 {
00327 for (int i = 0; i < audio_channels * buf_cnt; i++)
00328 buf[i] = bswap_16(buf[i]);
00329 }
00330 #endif
00331
00332 bool AVFormatWriter::WriteAudioFrame(unsigned char *buf, int fnum, int timecode)
00333 {
00334 #if HAVE_BIGENDIAN
00335 int sample_cnt = m_audioFrameSize / m_audioBytesPerSample;
00336 bswap_16_buf((short int*) buf, sample_cnt, m_audioChannels);
00337 #endif
00338
00339 int got_packet = 0;
00340 int ret = 0;
00341
00342 av_init_packet(m_audPkt);
00343 m_audPkt->data = m_audioOutBuf;
00344 m_audPkt->size = m_audioOutBufSize;
00345
00346 m_audPicture->data[0] = buf;
00347 m_audPicture->linesize[0] = m_audioFrameSize;
00348 m_audPicture->nb_samples = m_audioFrameSize;
00349 m_audPicture->format = AV_SAMPLE_FMT_S16;
00350
00351 {
00352 QMutexLocker locker(avcodeclock);
00353 ret = avcodec_encode_audio2(m_audioStream->codec, m_audPkt,
00354 m_audPicture, &got_packet);
00355 }
00356
00357 if (ret < 0 || !got_packet)
00358 {
00359 #if 0
00360 LOG(VB_RECORD, LOG_ERR, QString("WriteAudioFrame(): No Encoded Data: cs: %1, mfw: %2, tc: %3, fn: %4").arg(m_audPkt->size).arg(m_framesWritten).arg(timecode).arg(fnum));
00361 #endif
00362 return false;
00363 }
00364
00365 long long tc = timecode;
00366 if (m_startingTimecodeOffset == -1)
00367 m_startingTimecodeOffset = tc;
00368 tc -= m_startingTimecodeOffset;
00369
00370 if (m_avVideoCodec)
00371 m_audPkt->pts = tc * m_videoStream->time_base.den / m_videoStream->time_base.num / 1000;
00372 else
00373 m_audPkt->pts = tc * m_audioStream->time_base.den / m_audioStream->time_base.num / 1000;
00374
00375 m_audPkt->dts = AV_NOPTS_VALUE;
00376 m_audPkt->flags |= AV_PKT_FLAG_KEY;
00377 m_audPkt->data = (uint8_t*)m_audioOutBuf;
00378 m_audPkt->stream_index = m_audioStream->index;
00379
00380
00381
00382 ret = av_interleaved_write_frame(m_ctx, m_audPkt);
00383 if (ret != 0)
00384 LOG(VB_RECORD, LOG_ERR, LOC + "WriteAudioFrame(): "
00385 "av_interleaved_write_frame couldn't write Audio");
00386
00387 return true;
00388 }
00389
00390 bool AVFormatWriter::WriteTextFrame(int vbimode, unsigned char *buf, int len,
00391 int timecode, int pagenr)
00392 {
00393 return true;
00394 }
00395
00396 bool AVFormatWriter::ReOpen(QString filename)
00397 {
00398 bool result = m_ringBuffer->ReOpen(filename);
00399
00400 if (result)
00401 m_filename = filename;
00402
00403 return result;
00404 }
00405
00406 AVStream* AVFormatWriter::AddVideoStream(void)
00407 {
00408 AVCodecContext *c;
00409 AVStream *st;
00410
00411 st = avformat_new_stream(m_ctx, NULL);
00412 if (!st)
00413 {
00414 LOG(VB_RECORD, LOG_ERR,
00415 LOC + "AddVideoStream(): avformat_new_stream() failed");
00416 return NULL;
00417 }
00418 st->id = 0;
00419
00420 c = st->codec;
00421
00422 c->codec_id = m_ctx->oformat->video_codec;
00423 c->codec_type = AVMEDIA_TYPE_VIDEO;
00424 c->bit_rate = m_videoBitrate;
00425 c->width = m_width;
00426 c->height = m_height;
00427
00428
00429
00430
00431 c->time_base = GetCodecTimeBase();
00432
00433 st->time_base.den = 90000;
00434 st->time_base.num = 1;
00435 st->r_frame_rate.num = 0;
00436 st->r_frame_rate.den = 0;
00437
00438 c->gop_size = m_keyFrameDist;
00439 c->pix_fmt = PIX_FMT_YUV420P;
00440 c->thread_count = m_encodingThreadCount;
00441
00442 if (c->codec_id == CODEC_ID_MPEG2VIDEO) {
00443 c->max_b_frames = 2;
00444 }
00445 else if (c->codec_id == CODEC_ID_MPEG1VIDEO)
00446 {
00447 c->mb_decision = 2;
00448 }
00449 else if (c->codec_id == CODEC_ID_H264)
00450 {
00451 av_opt_set(c->priv_data, "profile", "baseline", 0);
00452
00453 if ((c->height <= 240) &&
00454 (c->width <= 320) &&
00455 (c->bit_rate <= 768000))
00456 {
00457 c->level = 13;
00458 }
00459 else if (c->width >= 960)
00460 {
00461 if (c->width >= 1024)
00462 c->level = 41;
00463 else
00464 c->level = 31;
00465
00466 av_opt_set(c->priv_data, "profile", "main", 0);
00467 }
00468 else
00469 {
00470 c->level = 30;
00471 }
00472
00473 c->coder_type = 0;
00474 c->max_b_frames = 0;
00475 c->slices = 8;
00476
00477 c->flags |= CODEC_FLAG_LOOP_FILTER;
00478 c->me_cmp |= 1;
00479 c->me_method = ME_HEX;
00480 c->me_subpel_quality = 6;
00481 c->me_range = 16;
00482 c->keyint_min = 25;
00483 c->scenechange_threshold = 40;
00484 c->i_quant_factor = 0.71;
00485 c->b_frame_strategy = 1;
00486 c->qcompress = 0.6;
00487 c->qmin = 10;
00488 c->qmax = 51;
00489 c->max_qdiff = 4;
00490 c->refs = 3;
00491 c->trellis = 0;
00492
00493 av_opt_set(c, "partitions", "i8x8,i4x4,p8x8,b8x8", 0);
00494 av_opt_set_int(c, "direct-pred", 1, 0);
00495 av_opt_set_int(c, "rc-lookahead", 0, 0);
00496 av_opt_set_int(c, "fast-pskip", 1, 0);
00497 av_opt_set_int(c, "mixed-refs", 1, 0);
00498 av_opt_set_int(c, "8x8dct", 0, 0);
00499 av_opt_set_int(c, "weightb", 0, 0);
00500 }
00501
00502 if(m_ctx->oformat->flags & AVFMT_GLOBALHEADER)
00503 c->flags |= CODEC_FLAG_GLOBAL_HEADER;
00504
00505
00506
00507 return st;
00508 }
00509
00510 bool AVFormatWriter::OpenVideo(void)
00511 {
00512 AVCodec *codec;
00513 AVCodecContext *c;
00514
00515 c = m_videoStream->codec;
00516
00517 codec = avcodec_find_encoder(c->codec_id);
00518 if (!codec)
00519 {
00520 LOG(VB_RECORD, LOG_ERR,
00521 LOC + "OpenVideo(): avcodec_find_encoder() failed");
00522 return false;
00523 }
00524
00525 if (avcodec_open2(c, codec, NULL) < 0)
00526 {
00527 LOG(VB_RECORD, LOG_ERR,
00528 LOC + "OpenVideo(): avcodec_open() failed");
00529 return false;
00530 }
00531
00532 m_videoOutBuf = NULL;
00533 if (!(m_ctx->oformat->flags & AVFMT_RAWPICTURE)) {
00534 m_videoOutBufSize = 200000;
00535 m_videoOutBuf = (unsigned char *)av_malloc(m_videoOutBufSize);
00536 }
00537
00538 m_picture = AllocPicture(c->pix_fmt);
00539 if (!m_picture)
00540 {
00541 LOG(VB_RECORD, LOG_ERR,
00542 LOC + "OpenVideo(): AllocPicture() failed");
00543 return false;
00544 }
00545
00546 m_tmpPicture = NULL;
00547 if (c->pix_fmt != PIX_FMT_YUV420P)
00548 {
00549 m_tmpPicture = AllocPicture(PIX_FMT_YUV420P);
00550 if (!m_tmpPicture)
00551 {
00552 LOG(VB_RECORD, LOG_ERR,
00553 LOC + "OpenVideo(): m_tmpPicture AllocPicture() failed");
00554 return false;
00555 }
00556 }
00557
00558 return true;
00559 }
00560
00561 AVStream* AVFormatWriter::AddAudioStream(void)
00562 {
00563 AVCodecContext *c;
00564 AVStream *st;
00565
00566 st = avformat_new_stream(m_ctx, NULL);
00567 if (!st)
00568 {
00569 LOG(VB_RECORD, LOG_ERR,
00570 LOC + "AddAudioStream(): avformat_new_stream() failed");
00571 return NULL;
00572 }
00573 st->id = 1;
00574
00575 c = st->codec;
00576 c->codec_id = m_ctx->oformat->audio_codec;
00577 c->codec_type = AVMEDIA_TYPE_AUDIO;
00578
00579 c->sample_fmt = AV_SAMPLE_FMT_S16;
00580 m_audioBytesPerSample = m_audioChannels * 2;
00581
00582 c->bit_rate = m_audioBitrate;
00583 c->sample_rate = m_audioSampleRate;
00584 c->channels = m_audioChannels;
00585
00586
00587
00588
00589 if (!m_avVideoCodec)
00590 {
00591 c->time_base = GetCodecTimeBase();
00592 st->time_base.den = 90000;
00593 st->time_base.num = 1;
00594 }
00595
00596
00597
00598
00599
00600
00601
00602 if(m_ctx->oformat->flags & AVFMT_GLOBALHEADER)
00603 c->flags |= CODEC_FLAG_GLOBAL_HEADER;
00604
00605 return st;
00606 }
00607
00608 bool AVFormatWriter::OpenAudio(void)
00609 {
00610 AVCodecContext *c;
00611 AVCodec *codec;
00612
00613 c = m_audioStream->codec;
00614
00615 c->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
00616
00617 codec = avcodec_find_encoder(c->codec_id);
00618 if (!codec)
00619 {
00620 LOG(VB_RECORD, LOG_ERR,
00621 LOC + "OpenAudio(): avcodec_find_encoder() failed");
00622 return false;
00623 }
00624
00625 if (avcodec_open2(c, codec, NULL) < 0)
00626 {
00627 LOG(VB_RECORD, LOG_ERR,
00628 LOC + "OpenAudio(): avcodec_open() failed");
00629 return false;
00630 }
00631
00632 m_audioFrameSize = c->frame_size;
00633
00634 m_audioOutBufSize = (int)(1.25 * 16384 * 7200);
00635
00636
00637 m_audioOutBuf = (unsigned char *)av_malloc(m_audioOutBufSize);
00638
00639 if (c->frame_size <= 1) {
00640 m_audioInputFrameSize = m_audioOutBufSize / c->channels;
00641 switch(m_audioStream->codec->codec_id) {
00642 case CODEC_ID_PCM_S16LE:
00643 case CODEC_ID_PCM_S16BE:
00644 case CODEC_ID_PCM_U16LE:
00645 case CODEC_ID_PCM_U16BE:
00646 m_audioInputFrameSize >>= 1;
00647 break;
00648 default:
00649 break;
00650 }
00651 } else {
00652 m_audioInputFrameSize = c->frame_size;
00653 }
00654 m_audioSamples =
00655 (unsigned int *)av_malloc(m_audioInputFrameSize * 2 * c->channels);
00656
00657 m_audPicture = avcodec_alloc_frame();
00658 if (!m_audPicture)
00659 {
00660 LOG(VB_RECORD, LOG_ERR,
00661 LOC + "OpenAudio(): alloc_frame() failed");
00662 return false;
00663 }
00664
00665 return true;
00666 }
00667
00668 AVFrame* AVFormatWriter::AllocPicture(enum PixelFormat pix_fmt)
00669 {
00670 AVFrame *picture;
00671 unsigned char *picture_buf;
00672 int size;
00673
00674 picture = avcodec_alloc_frame();
00675 if (!picture)
00676 {
00677 LOG(VB_RECORD, LOG_ERR,
00678 LOC + "AllocPicture(): avcodec_alloc_frame() failed");
00679 return NULL;
00680 }
00681 size = avpicture_get_size(pix_fmt, m_width, m_height);
00682 picture_buf = (unsigned char *)av_malloc(size);
00683 if (!picture_buf)
00684 {
00685 LOG(VB_RECORD, LOG_ERR, LOC + "AllocPicture(): av_malloc() failed");
00686 av_free(picture);
00687 return NULL;
00688 }
00689 avpicture_fill((AVPicture *)picture, picture_buf,
00690 pix_fmt, m_width, m_height);
00691 return picture;
00692 }
00693
00694 AVRational AVFormatWriter::GetCodecTimeBase(void)
00695 {
00696 AVRational result;
00697
00698 result.den = (int)floor(m_frameRate * 100);
00699 result.num = 100;
00700
00701 if (m_avVideoCodec && m_avVideoCodec->supported_framerates) {
00702 const AVRational *p= m_avVideoCodec->supported_framerates;
00703 AVRational req =
00704 (AVRational){result.den, result.num};
00705 const AVRational *best = NULL;
00706 AVRational best_error= (AVRational){INT_MAX, 1};
00707 for(; p->den!=0; p++) {
00708 AVRational error = av_sub_q(req, *p);
00709 if (error.num <0)
00710 error.num *= -1;
00711 if (av_cmp_q(error, best_error) < 0) {
00712 best_error = error;
00713 best = p;
00714 }
00715 }
00716
00717 if (best && best->num && best->den)
00718 {
00719 result.den = best->num;
00720 result.num = best->den;
00721 }
00722 }
00723
00724 if (result.den == 2997)
00725 {
00726 result.den = 30000;
00727 result.num = 1001;
00728 }
00729 else if (result.den == 5994)
00730 {
00731 result.den = 60000;
00732 result.num = 1001;
00733 }
00734
00735 return result;
00736 }
00737
00738
00739