mmfile.c

Go to the documentation of this file.
00001 /*
00002     Copyright (C) 2007 Krzysztof Kosciuszkiewicz
00003 
00004     This program is free software; you can redistribute it and/or modify
00005     it under the terms of the GNU General Public License as published by
00006     the Free Software Foundation; either version 2 of the License, or
00007     (at your option) any later version.
00008 
00009     This program is distributed in the hope that it will be useful,
00010     but WITHOUT ANY WARRANTY; without even the implied warranty of
00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012     GNU General Public License for more details.
00013 
00014     You should have received a copy of the GNU General Public License
00015     along with this program; if not, write to the Free Software
00016     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00017 */
00018 #include "race.h"
00019 #include "mmfile.h"
00020 #include "int_types.h"
00021 #include "macros.h"
00022 #include "utils.h"
00023 #include "logging.h"
00024 #include <math.h>
00025 #include <assert.h>
00026 #include <stdio.h>
00027 #include <memory.h>
00028 #include <stdlib.h>
00029 #include <limits.h>
00030 #include <ogg/ogg.h>
00031 #include <vorbis/codec.h>
00032 #include <theora/theora.h>
00033 #include <SDL.h>
00034 
00035 LOG_DEFAULT_CATEGORY(multimedia);
00036 
00037 /** --
00038  * 
00039  * \return -1 on error
00040  * \return  0 on end of file
00041  * \return  1 on successful page read
00042  */
00043 static int
00044 get_page(mm_file * mf, ogg_page * pg)
00045 {
00046     const int bufsize = 8192;
00047     char *p = NULL;
00048     int n = 0;
00049     int res = 0;
00050 
00051     assert(mf);
00052 
00053     while (0 == (res = ogg_sync_pageout(&mf->sync, pg)))
00054     {
00055         p = ogg_sync_buffer(&mf->sync, bufsize);
00056         if (!p)
00057         {
00058             ERROR1("ogg buffer synchronization failed");
00059             return -1;
00060         }
00061 
00062         if (0 == (n = fread(p, 1, bufsize, mf->file)))
00063             return (feof(mf->file)) ? 0 : -1;
00064 
00065         if (ogg_sync_wrote(&mf->sync, n))
00066         {
00067             ERROR1("buffer overflow in ogg_sync_wrote");
00068             return -1;
00069         }
00070     }
00071     /* XXX: following may segfault if non-ogg file is read */
00072     if (res < 0 || ogg_page_version(pg) != 0)
00073         return -1;
00074     return 1;
00075 }
00076 
00077 static int
00078 get_packet(mm_file * mf, ogg_packet * pkt, enum stream_type type)
00079 {
00080     ogg_stream_state *stream, *other;
00081     ogg_page pg;
00082     enum stream_type other_type;
00083     int rv = 0;
00084 
00085     assert(mf);
00086     assert(pkt);
00087     switch (type)
00088     {
00089         case MEDIA_VIDEO:
00090             assert(mf->video);
00091             stream = mf->video;
00092             other = mf->audio;
00093             other_type = MEDIA_AUDIO;
00094             break;
00095         case MEDIA_AUDIO:
00096             assert(mf->audio);
00097             stream = mf->audio;
00098             other = mf->video;
00099             other_type = MEDIA_VIDEO;
00100             break;
00101         default:
00102             WARNING2("bad stream type: %d", type);
00103             return -1;
00104     }
00105     if (mf->end_of_stream & type)
00106         return 0;
00107     while (0 == ogg_stream_packetout(stream, pkt))
00108     {
00109         rv = get_page(mf, &pg);
00110         if (rv <= 0)
00111             return rv;
00112         if (ogg_stream_pagein(stream, &pg) < 0)
00113         {
00114             if (other && ogg_stream_pagein(other, &pg) == 0)
00115             {
00116                 /*
00117                  * Got page from other stream. If user won't ever decode this
00118                  * then we need to clean up it here - otherwise read but not
00119                  * decoded packets would accumulate.
00120                  */
00121                 if (mf->drop_packets & other_type)
00122                 {
00123                     ogg_packet packet;
00124 
00125                     while (ogg_stream_packetout(other, &packet))
00126                         /* just drop packets */ ;
00127                 }
00128             }
00129             else
00130             {
00131                 INFO2("got page not associated with any stream, "
00132                     "serial 0x%x", ogg_page_serialno(&pg));
00133                 /*
00134                  * drop page. Ogg source code says ogg_page member pointers are
00135                  * initialized to static buffers, so there is no need to free
00136                  * anything.
00137                  */
00138             }
00139         }
00140     }
00141     mf->end_of_stream |= (!!pkt->e_o_s) * type;
00142     return 1;
00143 }
00144 
00145 static int
00146 init_theora(mm_file * mf, ogg_page * pg)
00147 {
00148     int pkts = 0;
00149     int res = 0;
00150     int rval = 0;
00151     theora_info *th_info = NULL;
00152     theora_comment th_comm;
00153     ogg_packet pkt;
00154     ogg_stream_state stream;
00155 
00156     assert(mf);
00157     th_info = xmalloc(sizeof(*mf->video_info));
00158     theora_info_init(th_info);
00159     theora_comment_init(&th_comm);
00160     ogg_stream_init(&stream, ogg_page_serialno(pg));
00161 
00162     if (ogg_page_packets(pg) != 1 || ogg_page_granulepos(pg) != 0)
00163         goto end;
00164 
00165     if (ogg_stream_pagein(&stream, pg))
00166         /* should not happen */
00167         goto end;
00168 
00169     /* Three first packets must go successfully through the loop. */
00170     for (pkts = 0; pkts < 3; ++pkts)
00171     {
00172         while ((res = ogg_stream_packetpeek(&stream, &pkt)) != 1)
00173         {
00174             if (res < 0
00175                 || get_page(mf, pg) <= 0
00176                 || ogg_stream_pagein(&stream, pg) < 0)
00177             {
00178                 rval = -1;
00179                 goto end;
00180             }
00181         }
00182         switch (theora_decode_header(th_info, &th_comm, &pkt))
00183         {
00184             case 0:
00185                 break;
00186             case OC_VERSION:
00187             case OC_NEWPACKET:
00188                 INFO1("incompatible theora file");
00189                 /* fall through */
00190             case OC_BADHEADER:
00191             default:
00192                 goto end;
00193         }
00194 
00195         /* decode successful so grab packet */
00196         ogg_stream_packetout(&stream, &pkt);
00197     }
00198 
00199     mf->video_ctx = xmalloc(sizeof(*mf->video_ctx));
00200     mf->video = xmalloc(sizeof(*mf->video));
00201     memcpy(mf->video, &stream, sizeof(stream));
00202     theora_decode_init(mf->video_ctx, th_info);
00203     mf->video_info = th_info;
00204     rval = 1;
00205   end:
00206     theora_comment_clear(&th_comm);
00207     if (rval <= 0)
00208     {
00209         ogg_stream_clear(&stream);
00210         theora_info_clear(th_info);
00211         free(th_info);
00212         mf->video_info = NULL;
00213     }
00214     return rval;
00215 }
00216 
00217 static int
00218 init_vorbis(mm_file * mf, ogg_page * pg)
00219 {
00220     int pkts = 0;
00221     int res = 0;
00222     int rval = 0;
00223     vorbis_block *vo_blk = NULL;
00224     vorbis_info *vo_info = NULL;
00225     vorbis_comment vo_comm;
00226     ogg_packet pkt;
00227     ogg_stream_state stream;
00228 
00229     assert(mf);
00230     vo_info = xmalloc(sizeof(*vo_info));
00231     vorbis_info_init(vo_info);
00232     vorbis_comment_init(&vo_comm);
00233     ogg_stream_init(&stream, ogg_page_serialno(pg));
00234 
00235     if (ogg_page_packets(pg) != 1 || ogg_page_granulepos(pg) != 0)
00236         goto end;
00237 
00238     if (ogg_stream_pagein(&stream, pg) < 0)
00239         /* should not happen */
00240         goto end;
00241 
00242     /* 
00243      * Three first packets must go successfully through the loop.
00244      */
00245     for (pkts = 0; pkts < 3; ++pkts)
00246     {
00247         while ((res = ogg_stream_packetpeek(&stream, &pkt)) != 1)
00248         {
00249             if (res < 0
00250                 || get_page(mf, pg) <= 0
00251                 || ogg_stream_pagein(&stream, pg) < 0)
00252             {
00253                 rval = -1;
00254                 goto end;
00255             }
00256         }
00257 
00258         switch (vorbis_synthesis_headerin(vo_info, &vo_comm, &pkt))
00259         {
00260             case 0:
00261                 break;
00262             case OV_EBADHEADER:
00263                 INFO1("bad vorbis header");
00264             case OV_ENOTVORBIS:
00265             default:
00266                 goto end;
00267         }
00268 
00269         /* decode successful so grab packet */
00270         ogg_stream_packetout(&stream, &pkt);
00271     }
00272     /* maybe print something about comment or etc? */
00273 
00274     mf->audio_ctx = xmalloc(sizeof(*mf->audio_ctx));
00275     mf->audio = xmalloc(sizeof(*mf->audio));
00276     vo_blk = xmalloc(sizeof(*vo_blk));
00277     memcpy(mf->audio, &stream, sizeof(stream));
00278     vorbis_synthesis_init(mf->audio_ctx, vo_info);
00279     vorbis_block_init(mf->audio_ctx, vo_blk);
00280     mf->audio_info = vo_info;
00281     mf->audio_blk = vo_blk;
00282     rval = 1;
00283   end:
00284     vorbis_comment_clear(&vo_comm);
00285     if (rval <= 0)
00286     {
00287         ogg_stream_clear(&stream);
00288         vorbis_info_clear(vo_info);
00289         free(vo_info);
00290     }
00291     return rval;
00292 }
00293 
00294 static int
00295 yuv_to_overlay(const mm_file * mf, const yuv_buffer * yuv, SDL_Overlay * ovl)
00296 {
00297     unsigned i, h, w, xoff, yoff;
00298     uint8_t *yp, *up, *vp;
00299 
00300     assert(mf);
00301     assert(yuv);
00302     assert(ovl);
00303 
00304     h = min(mf->video_info->frame_height, (unsigned) ovl->h);
00305     w = min(mf->video_info->frame_width, (unsigned) ovl->w);
00306     xoff = mf->video_info->offset_x;
00307     yoff = mf->video_info->offset_y;
00308 
00309     switch (ovl->format)
00310     {
00311         case SDL_IYUV_OVERLAY:
00312             up = yuv->u;
00313             vp = yuv->v;
00314             break;
00315         case SDL_YV12_OVERLAY:
00316             up = yuv->v;
00317             vp = yuv->u;
00318             break;
00319         default:
00320             WARNING1("only IYUV and YV12 SDL overlay formats supported");
00321             return -1;
00322     }
00323     yp = yuv->y;
00324 
00325     switch (mf->video_info->pixelformat)
00326     {
00327         case OC_PF_420:
00328             break;
00329         case OC_PF_422:
00330         case OC_PF_444:
00331         default:
00332             WARNING1("unknown/unsupported theora pixel format");
00333             return -1;
00334     }
00335 
00336     if (SDL_LockYUVOverlay(ovl) < 0)
00337     {
00338         WARNING1("unable to lock overlay");
00339         return -1;
00340     }
00341     /* luna goes first */
00342     for (i = 0; i < h; ++i)
00343     {
00344         memcpy(ovl->pixels[0] + i * ovl->pitches[0],
00345             yp + (i + yoff) * yuv->y_stride + xoff, w);
00346     }
00347     xoff /= 2;
00348     yoff /= 2;
00349     /* round up */
00350     w = w / 2 + w % 2;
00351     h = h / 2 + h % 2;
00352     /* handle 2x2 subsampled u and v planes */
00353     for (i = 0; i < h; ++i)
00354     {
00355         memcpy(ovl->pixels[1] + i * ovl->pitches[1],
00356             up + (i + yoff) * yuv->uv_stride + xoff, w);
00357         memcpy(ovl->pixels[2] + i * ovl->pitches[2],
00358             vp + (i + yoff) * yuv->uv_stride + xoff, w);
00359     }
00360     SDL_UnlockYUVOverlay(ovl);
00361     return 0;
00362 }
00363 
00364 /* rval < 0: error, > 0: have audio or video */
00365 int
00366 mm_open_fp(mm_file * mf, FILE * file)
00367 {
00368     int retval = -1;
00369     int res = 0;
00370     int have_vorbis = 0;
00371     int have_theora = 0;
00372     ogg_page pg;
00373 
00374     assert(mf);
00375     memset(mf, 0, sizeof(*mf));
00376 
00377     mf->file = file;
00378     if (!mf->file)
00379         return retval;
00380     ogg_sync_init(&mf->sync);
00381 
00382     /* get first page to start things up */
00383     if (get_page(mf, &pg) <= 0)
00384         goto err;
00385 
00386     DEBUG1("trying theora decoder...");
00387     res = init_theora(mf, &pg);
00388     if (res < 0)
00389         goto err;
00390     else
00391         have_theora = !!res * MEDIA_VIDEO;
00392 
00393     DEBUG1("trying vorbis decoder...");
00394     res = init_vorbis(mf, &pg);
00395     if (res < 0)
00396         goto err;
00397     else
00398         have_vorbis = !!res * MEDIA_AUDIO;
00399 
00400     if (have_vorbis)
00401     {
00402         unsigned c, r;
00403         mm_audio_info(mf, &c, &r);
00404         INFO3("audio %u channel(s) at %u Hz", c, r);
00405     }
00406     if (have_theora)
00407     {
00408         unsigned w, h; float fps;
00409         mm_video_info(mf, &w, &h, &fps);
00410         INFO4("video %ux%u pixels at %g fps", w, h, fps);
00411     }
00412     return have_vorbis | have_theora;
00413   err:
00414     WARNING1("unable to decode stream");
00415     mm_close(mf);
00416     return retval;
00417 }
00418 
00419 int
00420 mm_open(mm_file * mf, const char *fname)
00421 {
00422     assert(mf);
00423     assert(fname);
00424     INFO2("opening file `%s'", fname);
00425     return mm_open_fp(mf, fopen(fname, "rb"));
00426 }
00427 
00428 unsigned
00429 mm_ignore(mm_file * mf, unsigned mask)
00430 {
00431     unsigned old = mf->drop_packets;
00432 
00433     mf->drop_packets = mask;
00434     return old;
00435 }
00436 
00437 int
00438 mm_close(mm_file * mf)
00439 {
00440     assert(mf);
00441     if (mf->file)
00442     {
00443         fclose(mf->file);
00444         mf->file = NULL;
00445     }
00446     if (mf->audio)
00447     {
00448         ogg_stream_destroy(mf->audio);
00449         mf->audio = NULL;
00450     }
00451     if (mf->video)
00452     {
00453         ogg_stream_destroy(mf->video);
00454         mf->video = NULL;
00455     }
00456     if (mf->video_ctx)
00457     {
00458         theora_clear(mf->video_ctx);
00459         free(mf->video_ctx);
00460         mf->video_ctx = NULL;
00461     }
00462     if (mf->video_info)
00463     {
00464         theora_info_clear(mf->video_info);
00465         free(mf->video_info);
00466         mf->video_info = NULL;
00467     }
00468     if (mf->audio_blk)
00469     {
00470         vorbis_block_clear(mf->audio_blk);
00471         free(mf->audio_blk);
00472         mf->audio_blk = NULL;
00473     }
00474     if (mf->audio_ctx)
00475     {
00476         vorbis_dsp_clear(mf->audio_ctx);
00477         free(mf->audio_ctx);
00478         mf->audio_ctx = NULL;
00479     }
00480     if (mf->audio_info)
00481     {
00482         vorbis_info_clear(mf->audio_info);
00483         free(mf->audio_info);
00484         mf->audio_info = NULL;
00485     }
00486     ogg_sync_clear(&mf->sync);
00487     return 0;
00488 }
00489 
00490 /** 
00491  * \return rval < 0: no video in file 
00492  */
00493 int
00494 mm_video_info(const mm_file * mf, unsigned *width, unsigned *height,
00495     float *fps)
00496 {
00497     assert(mf);
00498     if (!mf->video)
00499         return -1;
00500     if (width)
00501         *width = mf->video_info->frame_width;
00502     if (height)
00503         *height = mf->video_info->frame_height;
00504     if (fps)
00505         *fps = mf->video_info->fps_numerator
00506             / mf->video_info->fps_denominator;
00507     return 1;
00508 }
00509 
00510 /**
00511  * \return rval < 0: no audio in file
00512  **/
00513 int
00514 mm_audio_info(const mm_file * mf, unsigned *channels, unsigned *rate)
00515 {
00516     assert(mf);
00517     if (!mf->audio)
00518         return -1;
00519     if (channels)
00520         *channels = mf->audio_info->channels;
00521     if (rate)
00522         *rate = mf->audio_info->rate;
00523     return 1;
00524 }
00525 
00526 int
00527 mm_decode_video(mm_file * mf, SDL_Overlay * ovl)
00528 {
00529     int rv = 0;
00530     ogg_packet pkt;
00531     yuv_buffer yuv;
00532 
00533     assert(mf);
00534     if (!mf->video)
00535         return -1;
00536     if (mf->drop_packets & MEDIA_VIDEO)
00537     {
00538         WARNING1("requested decode but MEDIA_VIDEO is set to ignore");
00539         return -1;
00540     }
00541     for (;;)
00542     {
00543         rv = get_packet(mf, &pkt, MEDIA_VIDEO);
00544         if (rv <= 0)
00545             return rv;
00546         /* we got packet, decode */
00547         if (theora_decode_packetin(mf->video_ctx, &pkt) == 0)
00548             break;
00549         else
00550         {
00551             WARNING1("packet does not contain theora frame");
00552             /* get next packet */
00553         }
00554     }
00555     theora_decode_YUVout(mf->video_ctx, &yuv);
00556     if (yuv_to_overlay(mf, &yuv, ovl) < 0)
00557         return -1;
00558     return 1;
00559 }
00560 
00561 /* for now just 8bit unsigned values, mono channels FIXME
00562  * maybe use SDL_AudioConvert() for this */
00563 int
00564 mm_decode_audio(mm_file * mf, void *buf, int buflen)
00565 {
00566     const int max_val = UCHAR_MAX;
00567     const int min_val = 0;
00568     const int bytes_per_sample = 1;
00569 
00570     int rv = 0, samples = 0, left = 0, total = 0;
00571     unsigned channels = 0;
00572 
00573     assert(mf);
00574     if (-1 == mm_audio_info(mf, &channels, NULL))
00575         return -1;
00576     if (mf->drop_packets & MEDIA_AUDIO)
00577     {
00578         WARNING1("requested decode but MEDIA_AUDIO is set to ignore");
00579         return -1;
00580     }
00581 
00582     /* convert buflen [bytes] to left [samples] */
00583     left = buflen;
00584     left = left / channels / bytes_per_sample;
00585 
00586     while (left > 0)
00587     {
00588         float **pcm;
00589         ogg_packet pkt;
00590 
00591         /* also outputs any samples left from last decoding */
00592         while (left > 0
00593             && (samples = vorbis_synthesis_pcmout(mf->audio_ctx, &pcm)) > 0)
00594         {
00595             int i = 0;
00596             unsigned ch = 0;
00597 
00598             samples = min(samples, left);
00599 
00600             for (i = 0; i < samples; ++i)
00601             {
00602                 for (ch = 0; ch < channels; ++ch)
00603                 {
00604                     /* XXX: lrint requires C99 */
00605                     int val = lrint((pcm[ch][i] + 1.0) / 2.0 * max_val);
00606 
00607                     if (val > max_val)
00608                         val = max_val;
00609                     if (val < min_val)
00610                         val = min_val;
00611                     *((uint8_t *) buf + (total + i) * channels + ch) = val;
00612                 }
00613             }
00614 
00615             total += samples;
00616             left -= samples;
00617             vorbis_synthesis_read(mf->audio_ctx, samples);
00618 
00619         }
00620         /* grab new packets if we need more */
00621         for (;;)
00622         {
00623             rv = get_packet(mf, &pkt, MEDIA_AUDIO);
00624             if (rv < 0)
00625                 return rv;
00626             else if (rv == 0)
00627                 return total * channels * bytes_per_sample;
00628 
00629             /* have packet, synthesize */
00630             if (vorbis_synthesis(mf->audio_blk, &pkt) == 0)
00631             {
00632                 vorbis_synthesis_blockin(mf->audio_ctx, mf->audio_blk);
00633                 break;
00634             }
00635             else
00636             {
00637                 WARNING1("packet does not contain a valid vorbis frame");
00638                 /* get next packet */
00639             }
00640         }
00641     }
00642 
00643     return total * channels * bytes_per_sample;
00644 }
00645 
00646 #if 0
00647 int
00648 mm_convert_audio(mm_file * mf, void *buf, int buflen, SDL_AudioSpec * spec)
00649 {
00650     SDL_AudioCVT cvt;
00651     unsigned channels, rate;
00652     uint8_t *tmp_buf = NULL;
00653     int allocated = 0;
00654     int to_decode = 0;
00655     int decoded = 0;
00656 
00657     assert(mf);
00658     assert(spec);
00659 
00660     if (-1 == mm_audio_info(mf, &channels, &rate))
00661         return -1;
00662 
00663     if (-1 == SDL_BuildAudioCVT(&cvt,
00664             MM_AUDIO_FORMAT, channels, rate,
00665             spec->format, spec->channels, spec->freq))
00666         return -1;
00667 
00668     /* Check if we need alloc memory or can dest buffer be used directly */
00669     to_decode = buflen / cvt.len_ratio;
00670     if (to_decode > buflen)
00671     {
00672         allocated = 1;
00673         tmp_buf = xmalloc(to_decode);
00674     }
00675     else
00676         tmp_buf = buf;
00677 
00678     decoded = mm_decode_audio(mf, tmp_buf, to_decode);
00679     if (decoded <= 0)
00680         return decoded;
00681 
00682     cvt.buf = tmp_buf;
00683     cvt.len = decoded;
00684 
00685     if (-1 == SDL_ConvertAudio(&cvt))
00686         return -1;
00687 
00688     if (allocated)
00689     {
00690         memcpy(buf, tmp_buf, cvt.len * cvt.len_ratio);
00691         free(tmp_buf);
00692     }
00693 
00694     return cvt.len * cvt.len_ratio;
00695 }
00696 #endif
00697 
00698 /* vi: set noet ts=4 sw=4 tw=78: */

Generated on Fri Sep 28 00:35:45 2007 for raceintospace by  doxygen 1.5.3