H.264 zmieniono na MP4, używając libavformat nie odtwarzającego

Próbuję zmiksować dane H.264 do pliku MP4. Wydaje się, że nie ma błędów w zapisywaniu danych H.264 załącznika B w pliku MP4, ale odtwarzanie pliku nie powiedzie się.

Dokonałem porównania plików binarnych i problem wydaje się być gdzieś w tym, co jest zapisywane w stopce (zwiastunie) pliku MP4.

Podejrzewam, że musi to być coś ze sposobem tworzenia strumienia lub czegoś takiego.

W tym:

AVOutputFormat* fmt = av_guess_format( 0, "out.mp4", 0 );
oc = avformat_alloc_context();
oc->oformat = fmt;
strcpy(oc->filename, filename);

Częścią tej prototypowej aplikacji jest tworzenie pliku png dla każdego IFrame. Więc kiedy napotkamy pierwszą ramkę IFrame, tworzę strumień wideo i piszę nagłówek av itd:

void addVideoStream(AVCodecContext* decoder)
{
    videoStream = av_new_stream(oc, 0);
    if (!videoStream)
    {
         cout << "ERROR creating video stream" << endl;
         return;        
    }
    vi = videoStream->index;    
    videoContext = videoStream->codec;      
    videoContext->codec_type = AVMEDIA_TYPE_VIDEO;
    videoContext->codec_id = decoder->codec_id;
    videoContext->bit_rate = 512000;
    videoContext->width = decoder->width;
    videoContext->height = decoder->height;
    videoContext->time_base.den = 25;
    videoContext->time_base.num = 1;    
    videoContext->gop_size = decoder->gop_size;
    videoContext->pix_fmt = decoder->pix_fmt;       

    if (oc->oformat->flags & AVFMT_GLOBALHEADER)
        videoContext->flags |= CODEC_FLAG_GLOBAL_HEADER;

    av_dump_format(oc, 0, filename, 1);

    if (!(oc->oformat->flags & AVFMT_NOFILE))
    {
        if (avio_open(&oc->pb, filename, AVIO_FLAG_WRITE) < 0) {
        cout << "Error opening file" << endl;
    }
    avformat_write_header(oc, NULL);
}

Piszę pakiety:

unsigned char* data = block->getData();
unsigned char videoFrameType = data[4];
int dataLen = block->getDataLen();

// store pps
if (videoFrameType == 0x68)
{
    if (ppsFrame != NULL)
    {
        delete ppsFrame; ppsFrameLength = 0; ppsFrame = NULL;
    }
    ppsFrameLength = block->getDataLen();
    ppsFrame = new unsigned char[ppsFrameLength];
    memcpy(ppsFrame, block->getData(), ppsFrameLength);
}
else if (videoFrameType == 0x67)
{
    // sps
    if (spsFrame != NULL)
    {
        delete spsFrame; spsFrameLength = 0; spsFrame = NULL;
}
    spsFrameLength = block->getDataLen();
    spsFrame = new unsigned char[spsFrameLength];
    memcpy(spsFrame, block->getData(), spsFrameLength);                 
}                                           

if (videoFrameType == 0x65 || videoFrameType == 0x41)
{
    videoFrameNumber++;
}
if (videoFrameType == 0x65)
{
    decodeIFrame(videoFrameNumber, spsFrame, spsFrameLength, ppsFrame, ppsFrameLength, data, dataLen);
}

if (videoStream != NULL)
{
    AVPacket pkt = { 0 };
    av_init_packet(&pkt);
    pkt.stream_index = vi;
    pkt.flags = 0;                      
    pkt.pts = pkt.dts = 0;                                  

    if (videoFrameType == 0x65)
    {
        // combine the SPS PPS & I frames together
        pkt.flags |= AV_PKT_FLAG_KEY;                                                   
        unsigned char* videoFrame = new unsigned char[spsFrameLength+ppsFrameLength+dataLen];
        memcpy(videoFrame, spsFrame, spsFrameLength);
        memcpy(&videoFrame[spsFrameLength], ppsFrame, ppsFrameLength);
        memcpy(&videoFrame[spsFrameLength+ppsFrameLength], data, dataLen);

        // overwrite the start code (00 00 00 01 with a 32-bit length)
        setLength(videoFrame, spsFrameLength-4);
        setLength(&videoFrame[spsFrameLength], ppsFrameLength-4);
        setLength(&videoFrame[spsFrameLength+ppsFrameLength], dataLen-4);
        pkt.size = dataLen + spsFrameLength + ppsFrameLength;
        pkt.data = videoFrame;
        av_interleaved_write_frame(oc, &pkt);
        delete videoFrame; videoFrame = NULL;
    }
    else if (videoFrameType != 0x67 && videoFrameType != 0x68)
    {   
        // Send other frames except pps & sps which are caught and stored                   
        pkt.size = dataLen;
        pkt.data = data;
        setLength(data, dataLen-4);                     
        av_interleaved_write_frame(oc, &pkt);
    }

Na koniec zamknij plik:

av_write_trailer(oc);
int i = 0;
for (i = 0; i < oc->nb_streams; i++)
{
    av_freep(&oc->streams[i]->codec);
    av_freep(&oc->streams[i]);      
}

if (!(oc->oformat->flags & AVFMT_NOFILE))
{
    avio_close(oc->pb);
}
av_free(oc);

Jeśli sam biorę dane H.264 i konwertuję je:

ffmpeg -i recording.h264 -vcodec copy recording.mp4

Wszystkie oprócz „stopki” plików są takie same.

Wyjście z mojego programu: readrec recording.tcp out.mp4 **** START **** 01-03-2013 14:26:01 180000 Wyjście # 0, mp4, do 'out.mp4': Strumień 0: 0 : Wideo: h264, yuv420p, 352x288, q = 2-31, 512 kb / s, 90k tbn, 25 tbc **** END **** 01-03-2013 14:27:01 102000 Napisano 1499 klatek wideo.

Jeśli spróbuję przekonwertować za pomocą ffmpeg, plik MP4 utworzony przy użyciu CODE:

ffmpeg -i out.mp4 -vcodec copy out2.mp4
ffmpeg version 0.11.1 Copyright (c) 2000-2012 the FFmpeg developers
      built on Mar  7 2013 12:49:22 with suncc 0x5110
      configuration: --extra-cflags=-KPIC -g --disable-mmx
      --disable-protocol=udp --disable-encoder=nellymoser --cc=cc --cxx=CC
libavutil      51. 54.100 / 51. 54.100
libavcodec     54. 23.100 / 54. 23.100
libavformat    54.  6.100 / 54.  6.100
libavdevice    54.  0.100 / 54.  0.100
libavfilter     2. 77.100 /  2. 77.100
libswscale      2.  1.100 /  2.  1.100
libswresample   0. 15.100 /  0. 15.100
h264 @ 12eaac0] no frame!
    Last message repeated 1 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
    Last message repeated 23 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
    Last message repeated 74 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
    Last message repeated 64 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
    Last message repeated 34 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
    Last message repeated 49 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
    Last message repeated 24 times
[h264 @ 12eaac0] Partitioned H.264 support is incomplete
[h264 @ 12eaac0] no frame!
    Last message repeated 23 times
[h264 @ 12eaac0] sps_id out of range
[h264 @ 12eaac0] no frame!
    Last message repeated 148 times
[h264 @ 12eaac0] sps_id (32) out of range
    Last message repeated 1 times
[h264 @ 12eaac0] no frame!
    Last message repeated 33 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
    Last message repeated 128 times
[h264 @ 12eaac0] sps_id (32) out of range
    Last message repeated 1 times
[h264 @ 12eaac0] no frame!
    Last message repeated 3 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
    Last message repeated 3 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
    Last message repeated 309 times
[h264 @ 12eaac0] sps_id (32) out of range
    Last message repeated 1 times
[h264 @ 12eaac0] no frame!
    Last message repeated 192 times
[h264 @ 12eaac0] Partitioned H.264 support is incomplete
[h264 @ 12eaac0] no frame!
    Last message repeated 73 times
[h264 @ 12eaac0] sps_id (32) out of range
    Last message repeated 1 times
[h264 @ 12eaac0] no frame!
    Last message repeated 99 times
[h264 @ 12eaac0] sps_id (32) out of range
    Last message repeated 1 times
[h264 @ 12eaac0] no frame!
    Last message repeated 197 times
[mov,mp4,m4a,3gp,3g2,mj2 @ 12e3100] decoding for stream 0 failed
[mov,mp4,m4a,3gp,3g2,mj2 @ 12e3100] Could not find codec parameters
(Video: h264 (avc1 / 0x31637661), 393539 kb/s)
out.mp4: could not find codec parameters

Naprawdę nie wiem, gdzie jest problem, chyba że ma to coś wspólnego ze sposobem, w jaki strumienie są tworzone. Przejrzałem fragmenty kodu, z których inni robią podobne rzeczy, i spróbowałem użyć tej porady przy konfigurowaniu strumieni, ale bezskutecznie!

Ostateczny kod, który dał mi plik mieszany (synchronizowany) H.264 / AAC, jest następujący. Najpierw trochę informacji w tle. Dane pochodzą z kamery IP. Dane są prezentowane za pośrednictwem zewnętrznego interfejsu API jako pakiety wideo / audio. Pakiety wideo są przedstawiane jako dane ładunku RTP (bez nagłówka) i składają się z NALU, które są odtwarzane i konwertowane na wideo H.264 w formacie załącznika B. Dźwięk AAC jest prezentowany jako surowy AAC i jest konwertowany na format adts, aby umożliwić odtwarzanie. Pakiety te zostały umieszczone w formacie strumienia bitów, który umożliwia transmisję znacznika czasu (64 milisekundy od 1 stycznia 1970) wraz z kilkoma innymi rzeczami.

Jest to mniej więcej prototyp i nie jest czysty pod żadnym względem. Prawdopodobnie nieszczelne. Mam jednak nadzieję, że pomoże to każdemu, kto próbuje osiągnąć coś podobnego do tego, czym jestem.

Globals:

AVFormatContext* oc = NULL;
AVCodecContext* videoContext = NULL;
AVStream* videoStream = NULL;
AVCodecContext* audioContext = NULL;
AVStream* audioStream = NULL;
AVCodec* videoCodec = NULL;
AVCodec* audioCodec = NULL;
int vi = 0;  // Video stream
int ai = 1;  // Audio stream

uint64_t firstVideoTimeStamp = 0;
uint64_t firstAudioTimeStamp = 0;
int audioStartOffset = 0;

char* filename = NULL;

Boolean first = TRUE;

int videoFrameNumber = 0;
int audioFrameNumber = 0;

Główny:

int main(int argc, char* argv[])
{
    if (argc != 3)
    {   
        cout << argv[0] << " <stream playback file> <output mp4 file>" << endl;
        return 0;
    }
    char* input_stream_file = argv[1];
    filename = argv[2];

    av_register_all();    

    fstream inFile;
    inFile.open(input_stream_file, ios::in);

    // Used to store the latest pps & sps frames
    unsigned char* ppsFrame = NULL;
    int ppsFrameLength = 0;
    unsigned char* spsFrame = NULL;
    int spsFrameLength = 0;

    // Setup MP4 output file
    AVOutputFormat* fmt = av_guess_format( 0, filename, 0 );
    oc = avformat_alloc_context();
    oc->oformat = fmt;
    strcpy(oc->filename, filename);

    // Setup the bitstream filter for AAC in adts format.  Could probably also achieve
    // this by stripping the first 7 bytes!
    AVBitStreamFilterContext* bsfc = av_bitstream_filter_init("aac_adtstoasc");
    if (!bsfc)
    {       
        cout << "Error creating adtstoasc filter" << endl;
        return -1;
    }

    while (inFile.good())
    {
        TcpAVDataBlock* block = new TcpAVDataBlock();
        block->readStruct(inFile);
        DateTime dt = block->getTimestampAsDateTime();
        switch (block->getPacketType())
        {
            case TCP_PACKET_H264:
            {       
                if (firstVideoTimeStamp == 0)
                    firstVideoTimeStamp = block->getTimeStamp();
                unsigned char* data = block->getData();
                unsigned char videoFrameType = data[4];
                int dataLen = block->getDataLen();

                // pps
                if (videoFrameType == 0x68)
                {
                    if (ppsFrame != NULL)
                    {
                        delete ppsFrame; ppsFrameLength = 0;
                        ppsFrame = NULL;
                    }
                    ppsFrameLength = block->getDataLen();
                    ppsFrame = new unsigned char[ppsFrameLength];
                    memcpy(ppsFrame, block->getData(), ppsFrameLength);
                }
                else if (videoFrameType == 0x67)
                {
                    // sps
                    if (spsFrame != NULL)
                    {
                        delete spsFrame; spsFrameLength = 0;
                        spsFrame = NULL;
                    }
                    spsFrameLength = block->getDataLen();
                    spsFrame = new unsigned char[spsFrameLength];
                    memcpy(spsFrame, block->getData(), spsFrameLength);                   
                }                                           

                if (videoFrameType == 0x65 || videoFrameType == 0x41)
                {
                    videoFrameNumber++;
                }
                // Extract a thumbnail for each I-Frame
                if (videoFrameType == 0x65)
                {
                    decodeIFrame(h264, spsFrame, spsFrameLength, ppsFrame, ppsFrameLength, data, dataLen);
                }
                if (videoStream != NULL)
                {
                    AVPacket pkt = { 0 };
                    av_init_packet(&pkt);
                    pkt.stream_index = vi;
                    pkt.flags = 0;           
                    pkt.pts = videoFrameNumber;
                    pkt.dts = videoFrameNumber;           
                    if (videoFrameType == 0x65)
                    {
                        pkt.flags = 1;                           

                        unsigned char* videoFrame = new unsigned char[spsFrameLength+ppsFrameLength+dataLen];
                        memcpy(videoFrame, spsFrame, spsFrameLength);
                        memcpy(&videoFrame[spsFrameLength], ppsFrame, ppsFrameLength);

                        memcpy(&videoFrame[spsFrameLength+ppsFrameLength], data, dataLen);
                        pkt.data = videoFrame;
                        av_interleaved_write_frame(oc, &pkt);
                        delete videoFrame; videoFrame = NULL;
                    }
                    else if (videoFrameType != 0x67 && videoFrameType != 0x68)
                    {                       
                        pkt.size = dataLen;
                        pkt.data = data;
                        av_interleaved_write_frame(oc, &pkt);
                    }                       
                }
                break;
            }

        case TCP_PACKET_AAC:

            if (firstAudioTimeStamp == 0)
            {
                firstAudioTimeStamp = block->getTimeStamp();
                uint64_t millseconds_difference = firstAudioTimeStamp - firstVideoTimeStamp;
                audioStartOffset = millseconds_difference * 16000 / 1000;
                cout << "audio offset: " << audioStartOffset << endl;
            }

            if (audioStream != NULL)
            {
                AVPacket pkt = { 0 };
                av_init_packet(&pkt);
                pkt.stream_index = ai;
                pkt.flags = 1;           
                pkt.pts = audioFrameNumber*1024;
                pkt.dts = audioFrameNumber*1024;
                pkt.data = block->getData();
                pkt.size = block->getDataLen();
                pkt.duration = 1024;

                AVPacket newpacket = pkt;                       
                int rc = av_bitstream_filter_filter(bsfc, audioContext,
                    NULL,
                    &newpacket.data, &newpacket.size,
                    pkt.data, pkt.size,
                    pkt.flags & AV_PKT_FLAG_KEY);

                if (rc >= 0)
                {
                    //cout << "Write audio frame" << endl;
                    newpacket.pts = audioFrameNumber*1024;
                    newpacket.dts = audioFrameNumber*1024;
                    audioFrameNumber++;
                    newpacket.duration = 1024;                   

                    av_interleaved_write_frame(oc, &newpacket);
                    av_free_packet(&newpacket);
                }   
                else
                {
                    cout << "Error filtering aac packet" << endl;

                }
            }
            break;

        case TCP_PACKET_START:
            break;

        case TCP_PACKET_END:
            break;
        }
        delete block;
    }
    inFile.close();

    av_write_trailer(oc);
    int i = 0;
    for (i = 0; i < oc->nb_streams; i++)
    {
        av_freep(&oc->streams[i]->codec);
        av_freep(&oc->streams[i]);       
    }

    if (!(oc->oformat->flags & AVFMT_NOFILE))
    {
        avio_close(oc->pb);
    }

    av_free(oc);

    delete spsFrame; spsFrame = NULL;
    delete ppsFrame; ppsFrame = NULL;

    cout << "Wrote " << videoFrameNumber << " video frames." << endl;

    return 0;
}

Strumień strumienia / kodeki są dodawane, a nagłówek jest tworzony w funkcji o nazwie addVideoAndAudioStream (). Ta funkcja jest wywoływana z decodeIFrame (), więc istnieje kilka założeń (które niekoniecznie są dobre) 1. Pierwszy pakiet wideo 2. AAC jest obecny

DecodeIFrame było swego rodzaju oddzielnym prototypem, w którym tworzyłem miniaturę dla każdej I Frame. Kod do generowania miniatur pochodzi z:https://gnunet.org/svn/Extractor/src/plugins/thumbnailffmpeg_extractor.c

Funkcja decodeIFrame przekazuje AVCodecContext do addVideoAudioStream:

void addVideoAndAudioStream(AVCodecContext* decoder = NULL)
{
    videoStream = av_new_stream(oc, 0);
    if (!videoStream)
    {
        cout << "ERROR creating video stream" << endl;
        return;       
    }
    vi = videoStream->index;   
    videoContext = videoStream->codec;       
    videoContext->codec_type = AVMEDIA_TYPE_VIDEO;
    videoContext->codec_id = decoder->codec_id;
    videoContext->bit_rate = 512000;
    videoContext->width = decoder->width;
    videoContext->height = decoder->height;
    videoContext->time_base.den = 25;
    videoContext->time_base.num = 1;
    videoContext->gop_size = decoder->gop_size;
    videoContext->pix_fmt = decoder->pix_fmt;       

    audioStream = av_new_stream(oc, 1);
    if (!audioStream)
    {
        cout << "ERROR creating audio stream" << endl;
        return;
    }
    ai = audioStream->index;
    audioContext = audioStream->codec;
    audioContext->codec_type = AVMEDIA_TYPE_AUDIO;
    audioContext->codec_id = CODEC_ID_AAC;
    audioContext->bit_rate = 64000;
    audioContext->sample_rate = 16000;
    audioContext->channels = 1;

    if (oc->oformat->flags & AVFMT_GLOBALHEADER)
    {
        videoContext->flags |= CODEC_FLAG_GLOBAL_HEADER;
        audioContext->flags |= CODEC_FLAG_GLOBAL_HEADER;
    }

    av_dump_format(oc, 0, filename, 1);

    if (!(oc->oformat->flags & AVFMT_NOFILE))
    {
        if (avio_open(&oc->pb, filename, AVIO_FLAG_WRITE) < 0) {
            cout << "Error opening file" << endl;
        }
    }

    avformat_write_header(oc, NULL);
}

O ile wiem, wiele założeń nie miało znaczenia, na przykład: 1. Szybkość transmisji. Rzeczywista szybkość transmisji wideo wynosiła ~ 262k, podczas gdy ja określiłem 512kbit 2. Kanały AAC. Podałem mono, chociaż rzeczywiste wyjście to Stereo z pamięci

Nadal musisz wiedzieć, jaka jest szybkość klatek (podstawa czasu) dla wideo i audio.

W przeciwieństwie do wielu innych przykładów, podczas ustawiania punktów i pakietów na pakietach wideo nie można było ich odtworzyć. Musiałem znać podstawę czasu (25fps), a następnie ustawić punkty i dts zgodnie z tą podstawą czasu, tj. Pierwsza ramka = 0 (PPS, SPS, I), druga ramka = 1 (ramka pośrednia, niezależnie od jej nazwy;)) .

AAC Musiałem również założyć, że było to 16000 Hz. 1024 próbki na pakiet AAC (myślę, że możesz mieć AAC @ 960 próbek), aby określić „offset” audio. Dodałem to do pts i dts. Tak więc pts / dts to numer próbki do odtworzenia. Musisz również upewnić się, że czas trwania 1024 jest ustawiony w pakiecie przed napisaniem.

-

Odkryłem dodatkowo, że dzisiaj załącznik B nie jest tak naprawdę kompatybilny z żadnym innym odtwarzaczem, więc format AVCC powinien być naprawdę używany.

Te adresy URL pomogły:Problem z dekodowaniem wideo H264 przez RTP za pomocą ffmpeg (libavcodec) http://aviadr1.blogspot.com.au/2010/05/h264-extradata-partially-explained-for.html

Podczas konstruowania strumienia wideo wypełniłem ekstradata i rozmiar_adresowy:

// Extradata contains PPS & SPS for AVCC format
int extradata_len = 8 + spsFrameLen-4 + 1 + 2 + ppsFrameLen-4;
videoContext->extradata = (uint8_t*)av_mallocz(extradata_len);
videoContext->extradata_size = extradata_len;
videoContext->extradata[0] = 0x01;
videoContext->extradata[1] = spsFrame[4+1];
videoContext->extradata[2] = spsFrame[4+2];
videoContext->extradata[3] = spsFrame[4+3];
videoContext->extradata[4] = 0xFC | 3;
videoContext->extradata[5] = 0xE0 | 1;
int tmp = spsFrameLen - 4;
videoContext->extradata[6] = (tmp >> 8) & 0x00ff;
videoContext->extradata[7] = tmp & 0x00ff;
int i = 0;
for (i=0;i<tmp;i++)
    videoContext->extradata[8+i] = spsFrame[4+i];
videoContext->extradata[8+tmp] = 0x01;
int tmp2 = ppsFrameLen-4;   
videoContext->extradata[8+tmp+1] = (tmp2 >> 8) & 0x00ff;
videoContext->extradata[8+tmp+2] = tmp2 & 0x00ff;
for (i=0;i<tmp2;i++)
    videoContext->extradata[8+tmp+3+i] = ppsFrame[4+i];

Podczas zapisywania ramek nie dodawaj ramek SPS i PPS, po prostu wypisz ramki I Frame & P. Ponadto zastąp kod początkowy załącznika B zawarty w pierwszych 4 bajtach (0x00 0x00 0x00 0x01) rozmiarem ramki I / P.

questionAnswers(1)

yourAnswerToTheQuestion