Sto provando a muxare i dati H.264 in un file MP4. Sembra che non ci siano errori nel salvataggio di questo H.264 allegato B dati in un file MP4, ma il file non riesce a riprodurre.H.264 muxed a MP4 utilizzando libavformat non riproducibile
Ho eseguito un confronto binario sui file e il problema sembra essere da qualche parte in quello che viene scritto nel footer (trailer) del file MP4.
Ho il sospetto che debba essere qualcosa con il modo in cui il flusso viene creato o qualcosa del genere.
Init:
AVOutputFormat* fmt = av_guess_format(0, "out.mp4", 0);
oc = avformat_alloc_context();
oc->oformat = fmt;
strcpy(oc->filename, filename);
Parte di questa applicazione prototipo di quello che ho è la creazione di un file PNG per ogni IFrame. Così, quando si incontra il primo IFrame, creo il flusso video e scrivere l'intestazione av ecc:
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);
}
scrivo pacchetti out:
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);
}
Infine per chiudere il file fuori:
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);
Se prendo solo i dati H.264 e convertirlo:
ffmpeg -i recording.h264 -vcodec copy recording.mp4
Tutti tranne il "footer" dei file sono gli stessi.
uscita dal mio programma: readrec recording.tcp out.mp4 **** **** AVVIARE 2013/01/03 14:26:01 180000 uscita # 0, mp4, a 'out.mp4 ': Stream # 0: 0: Video: h264, yuv420p, 352x288, q = 2-31, 512 kb/s, 90k tbn, 25 tbc **** END **** 01-03-2013 14: 27:01 102000 Ha scritto 1499 frame video.
Se provo a convertire utilizzando ffmpeg il file MP4 creato utilizzando codice:
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
Io davvero non so dove il problema è, tranne che deve essere qualcosa a che fare con il modo in cui i flussi sono in corso impostare. Ho visto bit di codice da dove altre persone stanno facendo una cosa simile, e ho cercato di usare questo consiglio nell'impostare i flussi, ma senza successo!
Il codice finale che mi ha fornito un file Muxed (sincronizzato) H.264/AAC è il seguente. Prima un po 'di informazioni di base. I dati provengono da una telecamera IP. I dati vengono presentati tramite un'API di terze parti come pacchetti video/audio. I pacchetti video sono presentati come dati del payload RTP (senza intestazione) e sono costituiti da NALU che vengono ricostruiti e convertiti in video H.264 nel formato Annex B. L'audio AAC viene presentato come AAC originale e viene convertito in formato Adts per consentire la riproduzione. Questi pacchetti sono stati inseriti in un formato bitstream che consente la trasmissione del timestamp (64 bit millisecondi dal 1 gennaio 1970) insieme ad alcune altre cose.
Questo è più o meno un prototipo e non è pulito sotto nessun aspetto. Probabilmente perde delle perdite. Tuttavia, spero che questo aiuti chiunque altro a cercare di ottenere qualcosa di simile a quello che sono.
Globali:
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;
principali:
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;
}
Il ruscello/codec sono aggiunti e l'intestazione viene creato in una funzione chiamata addVideoAndAudioStream(). Questa funzione viene chiamata da decodeIFrame() quindi ci sono alcune ipotesi (che non sono necessariamente buono) 1. Un pacchetto video viene prima 2. AAC è presente
La decodeIFrame era una specie di prototipo separata da dove Stavo creando una miniatura per ogni I Frame. Il codice per generare le miniature era da: https://gnunet.org/svn/Extractor/src/plugins/thumbnailffmpeg_extractor.c
La funzione decodeIFrame passa un AVCodecContext in 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);
}
Per quanto posso dire, un certo numero di ipotesi non sembra avere importanza, ad esempio: 1. Bit Rate. Il bit rate video effettivo era ~ 262k mentre ho specificato 512kbit 2. Canali AAC. Ho specificato mono, anche se l'output effettivo era Stereo dalla memoria
Avresti ancora bisogno di sapere qual è il frame rate (base dei tempi) per l'audio del video &.
Contrariamente a molti altri esempi, quando si impostano i pts & sui pacchetti video, non era riproducibile. Avevo bisogno di conoscere la base dei tempi (25fps) e quindi impostare i punti & in base a quella base di tempo, cioè primo frame = 0 (PPS, SPS, I), secondo frame = 1 (frame intermedio, indipendentemente dal suo nome;)) .
AAC Ho anche dovuto fare l'ipotesi che fosse 16000 hz. 1024 campioni per pacchetto AAC (È anche possibile avere AAC @ 960 campioni che penso) per determinare l'audio "offset". Ho aggiunto questo ai pts & dts. Quindi i pts/dts sono il numero del campione su cui deve essere riprodotto. È inoltre necessario assicurarsi che la durata di 1024 sia impostata nel pacchetto prima di scrivere anche.
-
Ho scoperto inoltre oggi che l'allegato B non è realmente compatibile con qualsiasi altro giocatore così formato AVCC in realtà dovrebbe essere utilizzato.
questi URL aiutato: Problem to Decode H264 video over RTP with ffmpeg (libavcodec) http://aviadr1.blogspot.com.au/2010/05/h264-extradata-partially-explained-for.html
Quando si costruisce il flusso video, ho compilato l'extradata & extradata_size:
// 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];
Quando si scrive i frame, non anteporre l'SPS & Cornici PPS, basta scrivere i frame I Frame & P. Inoltre, sostituire il codice di inizio allegato B contenuto nei primi 4 byte (0x00 0x00 0x00 0x01) con le dimensioni del frame I/P.
Perché combinare insieme SPS + PPS + I-frame per la scrittura? Inoltre, la funzione 'setLength()' potrebbe essere responsabile, ma questo è improbabile se il tuo binario di paragone con l'output di ffmpeg con il comando di remune non mostra differenze nel flusso. –
L'unione tra SPS e PPS e I Frame è stata un ripensamento. Li ho fatti separare inizialmente e non ha funzionato neanche allora. Li ho combinati perché quando faccio una decodifica sull'iframe occorreva le sp e i pps per decodificarlo e non li prendeva separatamente. setLength() sostituisce semplicemente il codice di avvio con una lunghezza di 32 bit e, come hai detto, nulla è diverso fino al piè di pagina. –
Va bene combinare SPS e PPS per il decodificatore, ma potrebbe essere pericoloso per il muxer (formato mp4). Credo anche che quando si inviano le fette a muxer, si dovrebbe rimuovere l'intestazione NALU. –