You've already forked FFmpeg
							
							
				mirror of
				https://github.com/FFmpeg/FFmpeg.git
				synced 2025-10-30 23:18:11 +02:00 
			
		
		
		
	mp3enc: write Xing TOC
Based on the code by: Peter Belkner <pbelkner@snafu.de>, Michael Niedermayer <michaelni@gmx.at>, Clément Bœsch <clement.boesch@smartjog.com>, Reimar Döffinger <Reimar.Doeffinger@gmx.de>, and Tobias Rapp <t.rapp@noa-audio.com> Alex Converse <alex.converse@gmail.com> Signed-off-by: Anton Khirnov <anton@khirnov.net>
This commit is contained in:
		| @@ -74,12 +74,27 @@ static int id3v1_create_tag(AVFormatContext *s, uint8_t *buf) | ||||
|     return count; | ||||
| } | ||||
|  | ||||
| #define XING_NUM_BAGS 400 | ||||
| #define XING_TOC_SIZE 100 | ||||
| // maximum size of the xing frame: offset/Xing/flags/frames/size/TOC | ||||
| #define XING_MAX_SIZE (32 + 4 + 4 + 4 + 4 + XING_TOC_SIZE) | ||||
|  | ||||
| typedef struct MP3Context { | ||||
|     const AVClass *class; | ||||
|     ID3v2EncContext id3; | ||||
|     int id3v2_version; | ||||
|     int write_id3v1; | ||||
|     int64_t nb_frames_offset; | ||||
|  | ||||
|     /* xing header */ | ||||
|     int64_t xing_offset; | ||||
|     int32_t frames; | ||||
|     int32_t size; | ||||
|     uint32_t want; | ||||
|     uint32_t seen; | ||||
|     uint32_t pos; | ||||
|     uint64_t bag[XING_NUM_BAGS]; | ||||
|     int initial_bitrate; | ||||
|     int has_variable_bitrate; | ||||
|  | ||||
|     /* index of the audio stream */ | ||||
|     int audio_stream_idx; | ||||
| @@ -92,15 +107,17 @@ typedef struct MP3Context { | ||||
|  | ||||
| static const uint8_t xing_offtbl[2][2] = {{32, 17}, {17, 9}}; | ||||
|  | ||||
| /* insert a dummy frame containing number of frames */ | ||||
| /* | ||||
|  * Write an empty XING header and initialize respective data. | ||||
|  */ | ||||
| static void mp3_write_xing(AVFormatContext *s) | ||||
| { | ||||
|     MP3Context       *mp3 = s->priv_data; | ||||
|     AVCodecContext *codec = s->streams[mp3->audio_stream_idx]->codec; | ||||
|     int       bitrate_idx = 1;    // 32 kbps | ||||
|     int32_t        header; | ||||
|     MPADecodeHeader  mpah; | ||||
|     int srate_idx, i, channels; | ||||
|     int bitrate_idx; | ||||
|     int xing_offset; | ||||
|     int ver = 0; | ||||
|  | ||||
| @@ -132,6 +149,9 @@ static void mp3_write_xing(AVFormatContext *s) | ||||
|              return; | ||||
|     } | ||||
|  | ||||
|     /* 64 kbps frame, should be large enough */ | ||||
|     bitrate_idx = (ver == 3) ? 5 : 8; | ||||
|  | ||||
|     /* dummy MPEG audio header */ | ||||
|     header  =  0xff                                  << 24; // sync | ||||
|     header |= (0x7 << 5 | ver << 3 | 0x1 << 1 | 0x1) << 16; // sync/audio-version/layer 3/no crc*/ | ||||
| @@ -141,17 +161,78 @@ static void mp3_write_xing(AVFormatContext *s) | ||||
|  | ||||
|     avpriv_mpegaudio_decode_header(&mpah, header); | ||||
|  | ||||
|     av_assert0(mpah.frame_size >= XING_MAX_SIZE); | ||||
|  | ||||
|     xing_offset = xing_offtbl[ver != 3][codec->channels == 1]; | ||||
|     ffio_fill(s->pb, 0, xing_offset); | ||||
|     mp3->xing_offset = avio_tell(s->pb); | ||||
|     ffio_wfourcc(s->pb, "Xing"); | ||||
|     avio_wb32(s->pb, 0x1);    // only number of frames | ||||
|     mp3->nb_frames_offset = avio_tell(s->pb); | ||||
|     avio_wb32(s->pb, 0); | ||||
|     avio_wb32(s->pb, 0x01 | 0x02 | 0x04);  // frames / size / TOC | ||||
|  | ||||
|     mpah.frame_size -= 4 + xing_offset + 4 + 4 + 4; | ||||
|     mp3->size = mpah.frame_size; | ||||
|     mp3->want = 1; | ||||
|  | ||||
|     avio_wb32(s->pb, 0);  // frames | ||||
|     avio_wb32(s->pb, 0);  // size | ||||
|  | ||||
|     // TOC | ||||
|     for (i = 0; i < XING_TOC_SIZE; i++) | ||||
|         avio_w8(s->pb, 255 * i / XING_TOC_SIZE); | ||||
|  | ||||
|     mpah.frame_size -= 4 + xing_offset + 4 + 4 + 4 + 4 + XING_TOC_SIZE; | ||||
|     ffio_fill(s->pb, 0, mpah.frame_size); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Add a frame to XING data. | ||||
|  * Following lame's "VbrTag.c". | ||||
|  */ | ||||
| static void mp3_xing_add_frame(MP3Context *mp3, AVPacket *pkt) | ||||
| { | ||||
|     int i; | ||||
|  | ||||
|     mp3->frames++; | ||||
|     mp3->seen++; | ||||
|     mp3->size += pkt->size; | ||||
|  | ||||
|     if (mp3->want == mp3->seen) { | ||||
|         mp3->bag[mp3->pos] = mp3->size; | ||||
|  | ||||
|         if (XING_NUM_BAGS == ++mp3->pos) { | ||||
|             /* shrink table to half size by throwing away each second bag. */ | ||||
|             for (i = 1; i < XING_NUM_BAGS; i += 2) | ||||
|                 mp3->bag[i / 2] = mp3->bag[i]; | ||||
|  | ||||
|             /* double wanted amount per bag. */ | ||||
|             mp3->want *= 2; | ||||
|             /* adjust current position to half of table size. */ | ||||
|             mp3->pos = XING_NUM_BAGS / 2; | ||||
|         } | ||||
|  | ||||
|         mp3->seen = 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static int mp3_write_audio_packet(AVFormatContext *s, AVPacket *pkt) | ||||
| { | ||||
|     MP3Context  *mp3 = s->priv_data; | ||||
|  | ||||
|     if (mp3->xing_offset && pkt->size >= 4) { | ||||
|         MPADecodeHeader c; | ||||
|  | ||||
|         avpriv_mpegaudio_decode_header(&c, AV_RB32(pkt->data)); | ||||
|  | ||||
|         if (!mp3->initial_bitrate) | ||||
|             mp3->initial_bitrate = c.bit_rate; | ||||
|         if ((c.bit_rate == 0) || (mp3->initial_bitrate != c.bit_rate)) | ||||
|             mp3->has_variable_bitrate = 1; | ||||
|  | ||||
|         mp3_xing_add_frame(mp3, pkt); | ||||
|     } | ||||
|  | ||||
|     return ff_raw_write_packet(s, pkt); | ||||
| } | ||||
|  | ||||
| static int mp3_queue_flush(AVFormatContext *s) | ||||
| { | ||||
|     MP3Context *mp3 = s->priv_data; | ||||
| @@ -162,7 +243,7 @@ static int mp3_queue_flush(AVFormatContext *s) | ||||
|     mp3_write_xing(s); | ||||
|  | ||||
|     while ((pktl = mp3->queue)) { | ||||
|         if (write && (ret = ff_raw_write_packet(s, &pktl->pkt)) < 0) | ||||
|         if (write && (ret = mp3_write_audio_packet(s, &pktl->pkt)) < 0) | ||||
|             write = 0; | ||||
|         av_free_packet(&pktl->pkt); | ||||
|         mp3->queue = pktl->next; | ||||
| @@ -172,30 +253,50 @@ static int mp3_queue_flush(AVFormatContext *s) | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| static void mp3_update_xing(AVFormatContext *s) | ||||
| { | ||||
|     MP3Context  *mp3 = s->priv_data; | ||||
|     int i; | ||||
|  | ||||
|     /* replace "Xing" identification string with "Info" for CBR files. */ | ||||
|     if (!mp3->has_variable_bitrate) { | ||||
|         avio_seek(s->pb, mp3->xing_offset, SEEK_SET); | ||||
|         ffio_wfourcc(s->pb, "Info"); | ||||
|     } | ||||
|  | ||||
|     avio_seek(s->pb, mp3->xing_offset + 8, SEEK_SET); | ||||
|     avio_wb32(s->pb, mp3->frames); | ||||
|     avio_wb32(s->pb, mp3->size); | ||||
|  | ||||
|     avio_w8(s->pb, 0);  // first toc entry has to be zero. | ||||
|  | ||||
|     for (i = 1; i < XING_TOC_SIZE; ++i) { | ||||
|         int j = i * mp3->pos / XING_TOC_SIZE; | ||||
|         int seek_point = 256LL * mp3->bag[j] / mp3->size; | ||||
|         avio_w8(s->pb, FFMIN(seek_point, 255)); | ||||
|     } | ||||
|  | ||||
|     avio_seek(s->pb, 0, SEEK_END); | ||||
| } | ||||
|  | ||||
| static int mp3_write_trailer(struct AVFormatContext *s) | ||||
| { | ||||
|     uint8_t buf[ID3v1_TAG_SIZE]; | ||||
|     MP3Context *mp3 = s->priv_data; | ||||
|  | ||||
|     if (mp3 && mp3->pics_to_write) { | ||||
|     if (mp3->pics_to_write) { | ||||
|         av_log(s, AV_LOG_WARNING, "No packets were sent for some of the " | ||||
|                "attached pictures.\n"); | ||||
|         mp3_queue_flush(s); | ||||
|     } | ||||
|  | ||||
|     /* write the id3v1 tag */ | ||||
|     if (mp3 && mp3->write_id3v1 && id3v1_create_tag(s, buf) > 0) { | ||||
|     if (mp3->write_id3v1 && id3v1_create_tag(s, buf) > 0) { | ||||
|         avio_write(s->pb, buf, ID3v1_TAG_SIZE); | ||||
|     } | ||||
|  | ||||
|     /* write number of frames */ | ||||
|     if (mp3 && mp3->nb_frames_offset) { | ||||
|         avio_seek(s->pb, mp3->nb_frames_offset, SEEK_SET); | ||||
|         avio_wb32(s->pb, s->streams[mp3->audio_stream_idx]->nb_frames); | ||||
|         avio_seek(s->pb, 0, SEEK_END); | ||||
|     } | ||||
|  | ||||
|     avio_flush(s->pb); | ||||
|     if (mp3->xing_offset) | ||||
|         mp3_update_xing(s); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
| @@ -209,7 +310,6 @@ AVOutputFormat ff_mp2_muxer = { | ||||
|     .audio_codec       = AV_CODEC_ID_MP2, | ||||
|     .video_codec       = AV_CODEC_ID_NONE, | ||||
|     .write_packet      = ff_raw_write_packet, | ||||
|     .write_trailer     = mp3_write_trailer, | ||||
|     .flags             = AVFMT_NOTIMESTAMPS, | ||||
| }; | ||||
| #endif | ||||
| @@ -251,7 +351,7 @@ static int mp3_write_packet(AVFormatContext *s, AVPacket *pkt) | ||||
|                 mp3->queue = pktl; | ||||
|             mp3->queue_end = pktl; | ||||
|         } else | ||||
|             return ff_raw_write_packet(s, pkt); | ||||
|             return mp3_write_audio_packet(s, pkt); | ||||
|     } else { | ||||
|         int ret; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user