mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-31 22:56:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			189 lines
		
	
	
	
		
			4.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			189 lines
		
	
	
	
		
			4.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "ffmpeg_source.hpp"
 | |
| 
 | |
| #include <stdexcept>
 | |
| 
 | |
| using namespace Mangle::Sound;
 | |
| 
 | |
| // Static output buffer. Not thread safe, but supports multiple
 | |
| // streams operated from the same thread.
 | |
| static uint8_t outBuf[AVCODEC_MAX_AUDIO_FRAME_SIZE];
 | |
| 
 | |
| static void fail(const std::string &msg)
 | |
| { throw std::runtime_error("FFMpeg exception: " + msg); }
 | |
| 
 | |
| // --- Loader ---
 | |
| 
 | |
| static bool init = false;
 | |
| 
 | |
| FFMpegLoader::FFMpegLoader(bool setup)
 | |
| {
 | |
|   if(setup && !init)
 | |
|     {
 | |
|       av_register_all();
 | |
|       av_log_set_level(AV_LOG_ERROR);
 | |
|       init = true;
 | |
|     }
 | |
| }
 | |
| 
 | |
| // --- Source ---
 | |
| 
 | |
| FFMpegSource::FFMpegSource(const std::string &file)
 | |
| {
 | |
|   std::string msg;
 | |
|   AVCodec *codec;
 | |
| 
 | |
|   if(av_open_input_file(&FmtCtx, file.c_str(), NULL, 0, NULL) != 0)
 | |
|     fail("Error loading audio file " + file);
 | |
| 
 | |
|   if(av_find_stream_info(FmtCtx) < 0)
 | |
|     {
 | |
|       msg = "Error in file stream " + file;
 | |
|       goto err;
 | |
|     }
 | |
| 
 | |
|   // Pick the first audio stream, if any
 | |
|   for(StreamNum = 0; StreamNum < FmtCtx->nb_streams; StreamNum++)
 | |
|     {
 | |
|       // Pick the first audio stream
 | |
|       if(FmtCtx->streams[StreamNum]->codec->codec_type == CODEC_TYPE_AUDIO)
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|   if(StreamNum == FmtCtx->nb_streams)
 | |
|     fail("File '" + file + "' didn't contain any audio streams");
 | |
| 
 | |
|   // Open the decoder
 | |
|   CodecCtx = FmtCtx->streams[StreamNum]->codec;
 | |
|   codec = avcodec_find_decoder(CodecCtx->codec_id);
 | |
| 
 | |
|   if(!codec || avcodec_open(CodecCtx, codec) < 0)
 | |
|     {
 | |
|       msg = "Error loading '" + file + "': ";
 | |
|       if(codec)
 | |
|         msg += "coded error";
 | |
|       else
 | |
|         msg += "no codec found";
 | |
|       goto err;
 | |
|     }
 | |
| 
 | |
|   // No errors, we're done
 | |
|   return;
 | |
| 
 | |
|   // Handle errors
 | |
|  err:
 | |
|   av_close_input_file(FmtCtx);
 | |
|   fail(msg);
 | |
| }
 | |
| 
 | |
| FFMpegSource::~FFMpegSource()
 | |
| {
 | |
|   avcodec_close(CodecCtx);
 | |
|   av_close_input_file(FmtCtx);
 | |
| }
 | |
| 
 | |
| void FFMpegSource::getInfo(int32_t *rate, int32_t *channels, int32_t *bits)
 | |
| {
 | |
|   if(rate) *rate = CodecCtx->sample_rate;
 | |
|   if(channels) *channels = CodecCtx->channels;
 | |
|   if(bits) *bits = 16;
 | |
| }
 | |
| 
 | |
| size_t FFMpegSource::read(void *data, size_t length)
 | |
| {
 | |
|   if(isEof) return 0;
 | |
| 
 | |
|   size_t left = length;
 | |
|   uint8_t *outPtr = (uint8_t*)data;
 | |
| 
 | |
|   // First, copy over any stored data we might be sitting on
 | |
|   {
 | |
|     size_t s = storage.size();
 | |
|     size_t copy = s;
 | |
|     if(s)
 | |
|       {
 | |
|         // Make sure there's room
 | |
|         if(copy > left)
 | |
|           copy = left;
 | |
| 
 | |
|         // Copy
 | |
|         memcpy(outPtr, &storage[0], copy);
 | |
|         outPtr += copy;
 | |
|         left -= copy;
 | |
| 
 | |
|         // Is there anything left in the storage?
 | |
|         assert(s>= copy);
 | |
|         s -= copy;
 | |
|         if(s)
 | |
|           {
 | |
|             assert(left == 0);
 | |
| 
 | |
|             // Move it to the start and resize
 | |
|             memmove(&storage[0], &storage[copy], s);
 | |
|             storage.resize(s);
 | |
|           }
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   // Next, get more input data from stream, and decode it
 | |
|   while(left)
 | |
|     {
 | |
|       AVPacket packet;
 | |
| 
 | |
|       // Get the next packet, if any
 | |
|       if(av_read_frame(FmtCtx, &packet) < 0)
 | |
|         break;
 | |
| 
 | |
|       // We only allow one stream per file at the moment
 | |
|       assert((int)StreamNum == packet.stream_index);
 | |
| 
 | |
|       // Decode the packet
 | |
|       int len = AVCODEC_MAX_AUDIO_FRAME_SIZE;
 | |
|       int tmp = avcodec_decode_audio2(CodecCtx, (int16_t*)outBuf,
 | |
|                                       &len, packet.data, packet.size);
 | |
|       assert(tmp < 0 || tmp == packet.size);
 | |
| 
 | |
|       // We don't need the input packet any longer
 | |
|       av_free_packet(&packet);
 | |
| 
 | |
|       if(tmp < 0)
 | |
|         fail("Error decoding audio stream");
 | |
| 
 | |
|       // Copy whatever data we got, and advance the pointer
 | |
|       if(len > 0)
 | |
|         {
 | |
|           // copy = how many bytes do we copy now
 | |
|           size_t copy = len;
 | |
|           if(copy > left)
 | |
|             copy = left;
 | |
| 
 | |
|           // len = how many bytes are left uncopied
 | |
|           len -= copy;
 | |
| 
 | |
|           // copy data
 | |
|           memcpy(outPtr, outBuf, copy);
 | |
| 
 | |
|           // left = how much space is left in the caller output
 | |
|           // buffer. This loop repeats as long left is > 0
 | |
|           left -= copy;
 | |
|           outPtr += copy;
 | |
|           assert(left >= 0);
 | |
| 
 | |
|           if(len > 0)
 | |
|             {
 | |
|               // There were uncopied bytes. Store them for later.
 | |
|               assert(left == 0);
 | |
|               storage.resize(len);
 | |
|               memcpy(&storage[0], outBuf, len);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   // End of loop. Return the number of bytes copied.
 | |
|   assert(left <= length);
 | |
| 
 | |
|   // If we're returning less than asked for, then we're done
 | |
|   if(left > 0)
 | |
|     isEof = true;
 | |
| 
 | |
|   return length - left;
 | |
| }
 |