当前位置:网站首页>Résoudre le problème de la durée inexacte du fichier audio AAC obtenu par ffmpeg

Résoudre le problème de la durée inexacte du fichier audio AAC obtenu par ffmpeg

2022-06-11 06:23:00 Tinghua M

Les tests récents ont proposé unbug,ijkAcquisaacDocumentdurationNon.,Envoyez - le - moi,Pas du tout.,InAEOu le systèmemediaplayerTout ce que j'ai3m48s(L'heure exacte estMMParserExtractor: ADTS: duration = 228010580us,Comme le montre la figure ci - dessous:),ijkCe qu'on a, c'est2m54s,Pendant la lecture,In2m54sLe flux est terminé quand,Mis dans la compilationffmpegMoyenne, Duration:C'est vraiment00:03:13.07,MaisVLC- Oui.3m53s,Ce fichier est aussi un travail fantastique!Les autres joueurs ne discutent pas pour le moment,J'espère juste queMMParserExtractorAvecIJKPlayerLa durée obtenue est toujours! 

1、Analyse des problèmes

Commençons par analyser ce problème,La ligne de commande regarde ce fichier,ffmpegCe que j'ai trouvé dans3m13s

Regardez attentivement la flèche rouge,Ça veut dire obtenirdurationEst calculé sur la base du débit binaire,Peut - être inexact.. Cette acquisition audio et vidéo info En cas de problème, nous pouvons généralement avformat_find_stream_infoLa fonction commence l'analyse.

Ça vient directement delogRegarde.,waring Apparaît dans utils.c/libavformatEn bas.

static void estimate_timings_from_bit_rate(AVFormatContext *ic)
{
    int64_t filesize, duration;
    int i, show_warning = 0;
    AVStream *st;
	
	av_log(ic, AV_LOG_WARNING, "-->ic->bit_rate:%lld\n",ic->bit_rate);
	//Par ici.logJe vois.,bitrate Je n'ai pas ,bitrate = 0
    /* if bit_rate is already set, we believe it */
    if (ic->bit_rate <= 0) {
        int64_t bit_rate = 0;
        for (i = 0; i < ic->nb_streams; i++) {
            st = ic->streams[i];
			
            if (st->codecpar->bit_rate <= 0 && st->internal->avctx->bit_rate > 0)
                st->codecpar->bit_rate = st->internal->avctx->bit_rate;
            if (st->codecpar->bit_rate > 0) {
                if (INT64_MAX - st->codecpar->bit_rate < bit_rate) {
                    bit_rate = 0;
                    break;
                }
                bit_rate += st->codecpar->bit_rate;
            } else if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && st->codec_info_nb_frames > 1) {
                // If we have a videostream with packets but without a bitrate
                // then consider the sum not known
                bit_rate = 0;
                break;
            }
        }
        // Voici un bitrate
        ic->bit_rate = bit_rate;
		av_log(ic, AV_LOG_WARNING, "-->ic->bit_rate:%lld\n",ic->bit_rate);
    }
    //DelogComme vous pouvez le voir,Ici.durationC'est aussi0

    /* if duration is already set, we believe it */
	av_log(ic, AV_LOG_WARNING,"-->ic->duration:%lld\n",ic->duration);
    if (ic->duration == AV_NOPTS_VALUE &&
        ic->bit_rate != 0) {
        filesize = ic->pb ? avio_size(ic->pb) : 0;
		av_log(ic, AV_LOG_WARNING,"-->ic->filesize:%lld\n",filesize);
        if (filesize > ic->internal->data_offset) {
            filesize -= ic->internal->data_offset;
            for (i = 0; i < ic->nb_streams; i++) {
                st      = ic->streams[i];
                if (   st->time_base.num <= INT64_MAX / ic->bit_rate
                    && st->duration == AV_NOPTS_VALUE) {
                    // Ceci est basé sur les octets de fichier *8 / Débit binaire pour calculer duration,Ici.cbr Ce calcul permet de calculer ,Mais sivbr( Dynamique du débit binaire ) Il y a un problème 
                    duration = av_rescale(8 * filesize, st->time_base.den,
                                          ic->bit_rate *
                                          (int64_t) st->time_base.num);
                //AcquisdurationCe n'est pas exact
                    st->duration = duration;
                    show_warning = 1;
                }
            }
        }
    }
    if (show_warning)
        av_log(ic, AV_LOG_WARNING,
               "Estimating duration from bitrate, this may be inaccurate\n");
}

La fonction ci - dessus est appelée utils.c/libavofrmat:

static void estimate_timings(AVFormatContext *ic, int64_t old_offset)
{
    int64_t file_size;

    /* get the file size, if possible */
    if (ic->iformat->flags & AVFMT_NOFILE) {
        file_size = 0;
    } else {
        file_size = avio_size(ic->pb);
        file_size = FFMAX(0, file_size);
    }
	av_log(ic, AV_LOG_WARNING, "->ic->iformat->name:%s\n", ic->iformat->name);
	av_log(ic, AV_LOG_WARNING, "->file_size:%lld\n", file_size);
	av_log(ic, AV_LOG_WARNING, "->ic->pb->seekable:%d\n", ic->pb->seekable);

    if ((!strcmp(ic->iformat->name, "mpeg") ||
         !strcmp(ic->iformat->name, "mpegts")) &&
        file_size && (ic->pb->seekable & AVIO_SEEKABLE_NORMAL)) {
        /* get accurate estimate from the PTSes */
        estimate_timings_from_pts(ic, old_offset);
        ic->duration_estimation_method = AVFMT_DURATION_FROM_PTS;
    } else if (has_duration(ic)) {
    //Si dansdemuxerDansdurationC'est
        /* at least one component has timings - we use them for all
         * the components */
        fill_all_stream_timings(ic);
        ic->duration_estimation_method = AVFMT_DURATION_FROM_STREAM;
    } else {
    // Ce fichier n'est pas disponible duration, Alors c'est ici que je vais 
        /* less precise: use bitrate info */
        estimate_timings_from_bit_rate(ic);
        ic->duration_estimation_method = AVFMT_DURATION_FROM_BITRATE;
    }
    update_stream_timings(ic);

    {
        int i;
        AVStream av_unused *st;
        for (i = 0; i < ic->nb_streams; i++) {
            st = ic->streams[i];
            av_log(ic, AV_LOG_TRACE, "stream %d: start_time: %0.3f duration: %0.3f\n", i,
                   (double) st->start_time * av_q2d(st->time_base),
                   (double) st->duration   * av_q2d(st->time_base));
        }
        av_log(ic, AV_LOG_TRACE,
                "format: start_time: %0.3f duration: %0.3f bitrate=%"PRId64" kb/s\n",
                (double) ic->start_time / AV_TIME_BASE,
                (double) ic->duration   / AV_TIME_BASE,
                (int64_t)ic->bit_rate / 1000);
    }
}

Appelez la méthode ci - dessus avformat_find_stream_info/utils.c/libavformatEn fonction.

2、 Recherche de solutions

La raison est déjà connue , Comment résoudre ce problème? ?

aacDeduration Comment obtenir ?

Voyons voir.androidDans le systèmelibstagefrightDans le cadreaacextractoreRéalisation

AACExtractor::AACExtractor(
        const sp<DataSource> &source, const sp<AMessage> &_meta)
    : mDataSource(source),
      mInitCheck(NO_INIT),
      mFrameDurationUs(0) {
    sp<AMessage> meta = _meta;

    if (meta == NULL) {
        String8 mimeType;
        float confidence;
        sp<AMessage> _meta;

        if (!SniffAAC(mDataSource, &mimeType, &confidence, &meta)) {
            return;
        }
    }

    int64_t offset;
    CHECK(meta->findInt64("offset", &offset));

    uint8_t profile, sf_index, channel, header[2];
    if (mDataSource->readAt(offset + 2, &header, 2) < 2) {
        return;
    }
//Accèsprofile
    profile = (header[0] >> 6) & 0x3;
// Obtenir l'index d'échantillonnage 
    sf_index = (header[0] >> 2) & 0xf;
// Obtenir le taux d'échantillonnage 
    uint32_t sr = get_sample_rate(sf_index);
    if (sr == 0) {
        return;
    }
//Accès
    channel = (header[0] & 0x1) << 2 | (header[1] >> 6);

    mMeta = MakeAACCodecSpecificData(profile, sf_index, channel);

    off64_t streamSize, numFrames = 0;
    size_t frameSize = 0;
    int64_t duration = 0;
//Obtenir la taille du fichier
    if (mDataSource->getSize(&streamSize) == OK) {
         while (offset < streamSize) {
         //Accèsadts Taille de chaque image 
            if ((frameSize = getAdtsFrameLength(source, offset, NULL)) == 0) {
                return;
            }

            mOffsetVector.push(offset);

            offset += frameSize;// Offset plus 
            numFrames ++;// Calculer le nombre de cadres 
        }
//*************** L'accent est mis ici , Voici une analyse de aac Le format du document sera expliqué plus en détail *************
        // Round up and get the duration
        mFrameDurationUs = (1024 * 1000000ll + (sr - 1)) / sr;
        duration = numFrames * mFrameDurationUs;//Nombre total de cadresxUnAAC Temps de lecture du cadre audio 
        mMeta->setInt64(kKeyDuration, duration);
    }

    mInitCheck = OK;
}

On verra.getAdtsFrameLength/AACExtractor.cpp/libstagefrgihtFonctions, Cette fonction est en fait basée sur adts Pour calculer chaque framesizeDe la taille de

static size_t getAdtsFrameLength(const sp<DataSource> &source, off64_t offset, size_t* headerSize) {
//CRC
    const size_t kAdtsHeaderLengthNoCrc = 7;
    const size_t kAdtsHeaderLengthWithCrc = 9;

    size_t frameSize = 0;
//Mot de synchronisation
    uint8_t syncword[2];
    if (source->readAt(offset, &syncword, 2) != 2) {
        return 0;
    }
    if ((syncword[0] != 0xff) || ((syncword[1] & 0xf6) != 0xf0)) {
        return 0;
    }
//0Non.crc,1Oui.crc
    uint8_t protectionAbsent;
	
    if (source->readAt(offset + 1, &protectionAbsent, 1) < 1) {
        return 0;
    }
    protectionAbsent &= 0x1;

    uint8_t header[3];
    if (source->readAt(offset + 3, &header, 3) < 3) {
        return 0;
    }
//AccèsframesizeTaille
    frameSize = (header[0] & 0x3) << 11 | header[1] << 3 | header[2] >> 5;

    // protectionAbsent is 0 if there is CRC
    size_t headSize = protectionAbsent ? kAdtsHeaderLengthNoCrc : kAdtsHeaderLengthWithCrc;
    if (headSize > frameSize) {
        return 0;
    }
    if (headerSize != NULL) {
        *headerSize = headSize;
    }

    return frameSize;
}

Le principe de mise en œuvre ci - dessus est basé sur AAC Le cadre original contient une période de temps 1024 Échantillonnage et données connexes .UnAAC Temps de lecture du cadre audio =UnAAC Nombre d'échantillons échantillonnés correspondant au cadre /Taux d'échantillonnage.Alors...aac Durée totale du fichier audio t=Nombre total de cadresxUnAAC Temps de lecture du cadre audio .

Regardez en basaacDedemuxer,Inaacdec.c/libavformatEn bas., J'ai trouvé une paire à l'intérieur aidf Il n'y a pas de traitement de la tête , Ça n'a pas d'importance .

AACPrésentation du format

La première chose à savoir estAACLes formats de fichiers sont:ADIFEtADTSDeux.,Parmi euxADIF(Audio Data Interchange Format Format d'échange de données audio) Le décodage doit être effectué au début clairement défini , Impossible de commencer au milieu du flux de données ;EtADTS(Audio Data Transport Stream Flux de données audio)C'est le contraire., Ce format est caractérisé par des mots synchrones ,Le décodage peut commencer n'importe où dans ce flux,Comme son nom,C'est une sorte deTS Stream dans un format similaire .

ADTS Chaque image du format a un en - tête , Avec des caractéristiques de flux , Convient à la transmission et au traitement du réseau ,EtADIFUne seule tête unifiée, Et les deux formats header Le format est également différent . Actuellement, le courant dominant est ADTSFormat.

ADTS AACLe format du fichier est le suivant:

ADTS_header

AAC ES

ADTS_header

AAC ES

ADTS_header

AAC ES

 DetailedAAC Voir cet article pour le format

AAC Format de fichier et calcul de la durée du fichier audio

Obtenir la durée par image :ffmpeg Capable de lire correctement chaque image nb_samples Et en général sample_rate, Donc la Division est la durée de chaque image .

AAC:Taille du cadre1024- Oui.sample,Le taux d'échantillonnage est de44100Hz , Durée de lecture du cadre :acc dur=1024/44100 = 0.02322s=23.22ms

Comment obtenir une durée précise? ?Ça devrait passer paradts frame header Prenez le nombre total de cadres * La valeur de la durée par cadre est duration.

3、Résoudre le problème

Maintenant, regardonsffmpeg Dans ce format demuxer, Ce format d'encapsulation de fichier raw ADTS AAC,Maintenant, regardonsaacdec.c/libavformat

//Accèsadts frame Longueur du cadre pour 
static int getAdtsFrameLength(AVFormatContext *s,int64_t offset,int* headerSize)
{
	int64_t filesize, position = avio_tell(s->pb);  
    filesize = avio_size(s->pb);
	//av_log(NULL, AV_LOG_WARNING, "hxk->getAdtsFrameLength.filesize:%d\n",filesize);
    const int kAdtsHeaderLengthNoCrc = 7;
    const int kAdtsHeaderLengthWithCrc = 9;
    int frameSize = 0;
    uint8_t syncword[2];
	avio_seek(s->pb, offset, SEEK_SET);
	// Lire le mot de synchronisation 
    if(avio_read(s->pb,&syncword, 2)!= 2){
		return 0;
	}
    if ((syncword[0] != 0xff) || ((syncword[1] & 0xf6) != 0xf0)) {
        return 0;
    }
	uint8_t protectionAbsent;
	avio_seek(s->pb, offset+1, SEEK_SET);
	//LireprotectionAbsent
    if (avio_read(s->pb, &protectionAbsent, 1) < 1) {
        return 0;
    }
    protectionAbsent &= 0x1;
    uint8_t header[3];
//Lireheader
	avio_seek(s->pb, offset+3, SEEK_SET);
    if (avio_read(s->pb, &header, 3) < 3) {
        return 0;
    }
    
    //Accèsframesize
    frameSize = (header[0] & 0x3) << 11 | header[1] << 3 | header[2] >> 5;
    // protectionAbsent is 0 if there is CRC
    int headSize = protectionAbsent ? kAdtsHeaderLengthNoCrc : kAdtsHeaderLengthWithCrc;
    if (headSize > frameSize) {
        return 0;
    }
    if (headerSize != NULL) {
        *headerSize = headSize;
    }
    return frameSize;
}
// Obtenir le taux d'échantillonnage à partir de l'indice de taux d'échantillonnage 
static uint32_t get_sample_rate(const uint8_t sf_index)
{
    static const uint32_t sample_rates[] =
    {
        96000, 88200, 64000, 48000, 44100, 32000,
        24000, 22050, 16000, 12000, 11025, 8000
    };

    if (sf_index < sizeof(sample_rates) / sizeof(sample_rates[0])) {
        return sample_rates[sf_index];
    }

    return 0;
}

//add end

Modifieradts_aac_read_headerFonctions

static int adts_aac_read_header(AVFormatContext *s)
{
    AVStream *st;
    uint16_t state;

    st = avformat_new_stream(s, NULL);
    if (!st)
        return AVERROR(ENOMEM);

    st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
    st->codecpar->codec_id   = s->iformat->raw_codec_id;
    st->need_parsing         = AVSTREAM_PARSE_FULL_RAW;

    ff_id3v1_read(s);
    if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) &&
        !av_dict_get(s->metadata, "", NULL, AV_DICT_IGNORE_SUFFIX)) {
        int64_t cur = avio_tell(s->pb);
        ff_ape_parse_tag(s);
        avio_seek(s->pb, cur, SEEK_SET);
    }

    // skip data until the first ADTS frame is found
    state = avio_r8(s->pb);
    while (!avio_feof(s->pb) && avio_tell(s->pb) < s->probesize) {
        state = (state << 8) | avio_r8(s->pb);
        if ((state >> 4) != 0xFFF)
            continue;
        avio_seek(s->pb, -2, SEEK_CUR);
        break;
    }
    if ((state >> 4) != 0xFFF)
        return AVERROR_INVALIDDATA;

    // LCM of all possible ADTS sample rates
    //avpriv_set_pts_info(st, 64, 1, 28224000);

//add by M
#if  1
	// La poignée renvoie au point de départ 
	avio_seek(s->pb, 0, SEEK_SET);
	uint8_t profile, sf_index, channel, header[2];
	// Le pointeur de fichier se déplace avant le début du fichier 2Octets
	avio_seek(s->pb, 2, SEEK_SET);
	if (avio_read(s->pb,&header, 2) < 2) {
		av_log(NULL, AV_LOG_ERROR, "avio_read header error!\n");
		return 0;
	}
	int64_t offset = 0;
	//Accèsprofile
	profile = (header[0] >> 6) & 0x3;
	st->codecpar->profile = profile;
	sf_index = (header[0] >> 2) & 0xf;
	// Obtenir le taux d'échantillonnage 
	uint32_t sr = get_sample_rate(sf_index);
	if (sr == 0) {
		av_log(NULL, AV_LOG_ERROR, "adts_aac_read_header read sampletare error!\n");
		return 0;
	}
	//st->codecpar->sample_rate = sr;
	channel = (header[0] & 0x1) << 2 | (header[1] >> 6);
	if(channel == 0) {
		av_log(NULL, AV_LOG_ERROR, "adts_aac_read_header read channel error!\n");
		return 0;
	}
	//Assigner une valeur àcodec Paramètres
	st->codecpar->channels = channel;
	sf_index = (header[0] >> 2) & 0xf;
	int frameSize = 0;
	int64_t mFrameDurationUs = 0;
	int64_t duration = 0;
	// Taux d'échantillonnage attribué à codec
	st->codecpar->sample_rate = sr;
	int64_t streamSize, numFrames = 0;
	avpriv_set_pts_info(st, 64, 1, st->codecpar->sample_rate);
	//Obtenir la taille du fichier
	streamSize = avio_size(s->pb);
	if (streamSize > 0) {
		while (offset < streamSize) {
			if ((frameSize = getAdtsFrameLength(s, offset, NULL)) == 0) {
				goto  end;
			}
			offset += frameSize;
			// Nombre de cadres plus ,Obtenir le nombre total de cadres
			numFrames ++;
		}
end:
		av_log(NULL, AV_LOG_WARNING, "---streamSize:%lld,numFrames:%lld!---\n",streamSize, numFrames);
		// Round up and get the duration, Calculer le temps de chaque image 
		mFrameDurationUs = (1024 * 1000000ll + (sr - 1)) / sr;
		av_log(NULL, AV_LOG_WARNING, "---mFrameDurationUs:%lld!---\n",mFrameDurationUs);
		duration = numFrames * mFrameDurationUs; //us
		duration = av_rescale_q(duration,AV_TIME_BASE_Q, st->time_base);
		st->duration = duration;
		av_log(NULL, AV_LOG_WARNING, "-------duration:%d------!\n",duration);
	}
	// Remettre la poignée 
	avio_seek(s->pb, 0, SEEK_SET);
#endif
	//add end

    return 0;
}

À l'origine, il faisait référence au blog d'une fleur de pêche qui écrase Begonia ,Ici.return 0De,Testé,SectionaacLe fichier ne peut pas être lu, A été remplacé par le texte ci - dessus. gotoC'est

if ((frameSize = getAdtsFrameLength(s, offset, NULL)) == 0) {
				return 0;
			}

Aucun problème avec le test actuel ,C'est normal.seek Avec la lecture !

Liens de référence:ffmpegSérie-RésolutionffmpegAccèsaacFichiers audiodurationNon._ Un blog de fleurs de pêche sur Begonia -CSDNBlogs_ffmpeg Audioduration

原网站

版权声明
本文为[Tinghua M]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/162/202206110620188004.html