Obwohl diese Informationen bereits veraltet sind, ist das Originalmaterial immer noch eine beliebte Inspirationsquelle für verschiedene nützliche Inhalte zum Thema FFmpeg. Es gibt jedoch noch keine vollständige Übersetzung des Originals ins Russische. Wir korrigieren das nervige Auslassen, denn es ist besser spät als nie.Und obwohl wir es versucht haben, sind Schwierigkeiten bei der Übersetzung in einem so umfangreichen Text unvermeidlich . Fehler melden (vorzugsweise in privaten Nachrichten) - gemeinsam werden wir es besser machen.Inhaltsverzeichnis

EDISON.
, C C++.
! ;-)
UPD: Dieser Leitfaden wurde ab Februar 2015 aktualisiert.FFmpeg ist eine großartige Bibliothek zum Erstellen von Videoanwendungen sowie allgemeinen Dienstprogrammen. FFmpeg kümmert sich um die gesamte Videoverarbeitungsroutine und führt alle Decodierungen, Codierungen, Multiplexe und Demultiplexe durch. Dies vereinfacht die Erstellung von Medienanwendungen erheblich. Alles ist ganz einfach und schnell, in C geschrieben, Sie können fast jeden heute verfügbaren Codec dekodieren sowie in einige andere Formate kodieren.Der einzige Haken ist, dass die Dokumentation meistens fehlt. Es gibt ein Tutorial ( im Original befindet sich hier ein Link zu einer bereits nicht vorhandenen Webseite - Notizübersetzer), das die Grundlagen von FFmpeg und die automatische Erzeugung von Sauerstoffdocks behandelt. Und nichts weiter. Aus diesem Grund habe ich mich entschlossen, unabhängig herauszufinden, wie FFmpeg zum Erstellen funktionierender digitaler Video- und Audioanwendungen verwendet werden kann, und gleichzeitig den Prozess zu dokumentieren und in Form eines Lehrbuchs darzustellen.Es gibt ein FFplay-Programm, das mit FFmpeg geliefert wird. Es ist einfach, in C geschrieben, implementiert einen vollwertigen Videoplayer mit FFmpeg. Meine erste Lektion ist eine aktualisierte Version der Originalstunde von Martin Böhme ( im Original ein Link zu einer bereits nicht mehr existierenden Webseite - eine Anmerkung des Übersetzers ) - ich habe einige Teile von dort gezogen. Außerdem werde ich in einer Reihe meiner Lektionen den Prozess der Erstellung eines funktionierenden Videoplayers auf der Basis von ffplay.c zeigenFabrice Bellard. In jeder Lektion wird eine neue Idee (oder sogar zwei) mit einer Erläuterung ihrer Umsetzung vorgestellt. Jedes Kapitel enthält eine C-Liste, die Sie selbst kompilieren und ausführen können. Die Quelldateien zeigen, wie dieses Programm funktioniert, wie seine einzelnen Teile funktionieren und zeigen kleinere technische Details, die in diesem Handbuch nicht behandelt werden. Wenn wir fertig sind, haben wir einen funktionierenden Videoplayer, der in weniger als 1000 Codezeilen geschrieben ist!Beim Erstellen des Players verwenden wir SDL, um Audio- und Videomediendateien auszugeben. SDL ist eine hervorragende plattformübergreifende Multimedia-Bibliothek, die in MPEG-Wiedergabeprogrammen, Emulatoren und vielen Videospielen verwendet wird. Sie müssen die SDL-Bibliotheken herunterladen und auf Ihrem System installieren, um die Programme aus diesem Handbuch zu kompilieren.Dieses Tutorial richtet sich an Personen mit guter Programmiererfahrung. Zumindest müssen Sie C kennen und Konzepte wie Warteschlangen, Mutexe usw. verstehen. Es sollte ein gewisses Verständnis für Multimedia geben. Zum Beispiel Dinge wie Wellenformen und dergleichen. Es ist jedoch nicht notwendig, in diesen Angelegenheiten ein Guru zu sein, da viele Konzepte im Verlauf des Unterrichts erklärt werden.Bitte senden Sie mir Fehlermeldungen, Fragen, Kommentare, Ideen, Funktionen usw. an Dranger Doggy Gmail dot Com.

Lesen Sie auch im Blog der
Firma EDISON:
FFmpeg libav Handbuch
Lektion 1: Erstellen von Screencaps ← ⇑ →
Vollständige Auflistung: tutorial01.c
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <stdio.h>
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif
void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
FILE *pFile;
char szFilename[32];
int y;
sprintf(szFilename, "frame%d.ppm", iFrame);
pFile=fopen(szFilename, "wb");
if(pFile==NULL)
return;
fprintf(pFile, "P6\n%d %d\n255\n", width, height);
for(y=0; y<height; y++)
fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
fclose(pFile);
}
int main(int argc, char *argv[]) {
AVFormatContext *pFormatCtx = NULL;
int i, videoStream;
AVCodecContext *pCodecCtxOrig = NULL;
AVCodecContext *pCodecCtx = NULL;
AVCodec *pCodec = NULL;
AVFrame *pFrame = NULL;
AVFrame *pFrameRGB = NULL;
AVPacket packet;
int frameFinished;
int numBytes;
uint8_t *buffer = NULL;
struct SwsContext *sws_ctx = NULL;
if(argc < 2) {
printf("Please provide a movie file\n");
return -1;
}
av_register_all();
if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
return -1;
if(avformat_find_stream_info(pFormatCtx, NULL)<0)
return -1;
av_dump_format(pFormatCtx, 0, argv[1], 0);
videoStream=-1;
for(i=0; i<pFormatCtx->nb_streams; i++)
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
videoStream=i;
break;
}
if(videoStream==-1)
return -1;
pCodecCtxOrig=pFormatCtx->streams[videoStream]->codec;
pCodec=avcodec_find_decoder(pCodecCtxOrig->codec_id);
if(pCodec==NULL) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
pCodecCtx = avcodec_alloc_context3(pCodec);
if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1;
}
if(avcodec_open2(pCodecCtx, pCodec, NULL)<0)
return -1;
pFrame=av_frame_alloc();
pFrameRGB=av_frame_alloc();
if(pFrameRGB==NULL)
return -1;
numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
pCodecCtx->height);
buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
pCodecCtx->width, pCodecCtx->height);
sws_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
PIX_FMT_RGB24,
SWS_BILINEAR,
NULL,
NULL,
NULL
);
i=0;
while(av_read_frame(pFormatCtx, &packet)>=0) {
if(packet.stream_index==videoStream) {
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
if(frameFinished) {
sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
pFrame->linesize, 0, pCodecCtx->height,
pFrameRGB->data, pFrameRGB->linesize);
if(++i<=5)
SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height,
i);
}
}
av_free_packet(&packet);
}
av_free(buffer);
av_frame_free(&pFrameRGB);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avcodec_close(pCodecCtxOrig);
avformat_close_input(&pFormatCtx);
return 0;
}
Überblick
Filmdateien bestehen aus mehreren Hauptkomponenten. Zunächst wird die Datei selbst als Container bezeichnet , und der Containertyp bestimmt, wie Daten in der Datei dargestellt werden. Beispiele für Container sind AVI und Quicktime . Darüber hinaus enthält die Datei mehrere Threads. Insbesondere gibt es normalerweise einen Audiostream und einen Videostream . ("Stream" ist ein lustiges Wort für "eine Folge von Datenelementen, die gemäß der Zeitachse verfügbar sind".) Datenelemente in einem Stream werden als Frames bezeichnet . Jeder Stream wird von dem einen oder anderen Codec- Typ codiert . Der Codec bestimmt, wie die tatsächlichen Daten zu diruyutsya und DezemberGeprüft - daher der Name des Codecs. Beispiele für Codecs sind DivX und MP3. Pakete werden dann aus dem Stream gelesen. Pakete sind Datenstücke , die Datenbits enthalten können , die in Rohrahmen dekodiert werden, die wir schließlich in unserer Anwendung bearbeiten können. Für unsere Zwecke enthält jedes Paket Vollbilder (oder mehrere Bilder, wenn es sich um Audio handelt).Das Arbeiten mit Video- und Audiostreams ist selbst auf der einfachsten Ebene sehr einfach:10 OPEN video_stream FROM video.avi
20 READ packet FROM video_stream INTO frame
30 IF frame NOT COMPLETE GOTO 20
40 DO SOMETHING WITH frame
50 GOTO 20
Das Arbeiten mit Multimedia mit FFmpeg ist fast so einfach wie in diesem Programm, obwohl in einigen Programmen der Schritt "MAKE [...]" sehr schwierig sein kann. In diesem Tutorial öffnen wir die Datei, zählen den darin enthaltenen Videostream und unser "MAKE [...]" schreibt den Frame in die PPM-Datei.Datei öffnen
Lassen Sie uns zuerst sehen, was zuerst passiert, wenn Sie die Datei öffnen. Mit FFmpeg initialisieren wir zuerst die gewünschte Bibliothek:#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <ffmpeg/swscale.h>
...
int main(int argc, charg *argv[]) {
av_register_all();
Dadurch werden alle verfügbaren Dateiformate und Codecs in der Bibliothek registriert, sodass sie beim Öffnen einer Datei mit dem entsprechenden Format / Codec automatisch verwendet werden. Beachten Sie, dass Sie av_register_all () nur einmal aufrufen müssen , daher tun wir dies hier in main (). Wenn Sie möchten, können Sie nur ausgewählte Dateiformate und Codecs registrieren. In der Regel gibt es jedoch keinen besonderen Grund dafür.Öffnen Sie nun die Datei:AVFormatContext *pFormatCtx = NULL;
if(avformat_open_input(&pFormatCtx, argv[1], NULL, 0, NULL)!=0)
return -1;
Holen Sie sich den Dateinamen aus dem ersten Argument. Diese Funktion liest den Dateikopf und speichert die Dateiformatinformationen in der von uns übergebenen AVFormatContext- Struktur . Die letzten drei Argumente werden verwendet, um das Dateiformat, die Puffergröße und die Formatparameter anzugeben. Wenn Sie sie auf NULL oder 0 setzen, erkennt libavformat alles automatisch.Diese Funktion betrachtet nur den Header, daher müssen wir jetzt die Stream-Informationen in der Datei überprüfen:
if(avformat_find_stream_info(pFormatCtx, NULL)<0)
return -1;
Diese Funktion übergibt gültige Daten an pFormatCtx -> Streams . Wir lernen eine praktische Debugging-Funktion kennen, die uns zeigt, was drin ist:
av_dump_format(pFormatCtx, 0, argv[1], 0);
Jetzt ist pFormatCtx -> Streams nur ein Array von Zeigern der Größe pFormatCtx -> nb_streams . Wir werden es durchgehen, bis wir den Videostream finden:int i;
AVCodecContext *pCodecCtxOrig = NULL;
AVCodecContext *pCodecCtx = NULL;
videoStream=-1;
for(i=0; i<pFormatCtx->nb_streams; i++)
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
videoStream=i;
break;
}
if(videoStream==-1)
return -1;
pCodecCtx=pFormatCtx->streams[videoStream]->codec;
Informationen zum Codec im Stream befinden sich an einem Ort, der als " Codec-Kontext " bezeichnet wird. Es enthält alle Informationen über den Codec, den der Stream verwendet, und jetzt haben wir einen Zeiger darauf. Aber wir müssen noch den richtigen Codec finden und öffnen:AVCodec *pCodec = NULL;
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
pCodecCtx = avcodec_alloc_context3(pCodec);
if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1;
}
if(avcodec_open2(pCodecCtx, pCodec)<0)
return -1;
Bitte beachten Sie, dass Sie AVCodecContext nicht direkt aus dem Videostream verwenden können! Daher müssen Sie einen vcodec_copy_context () verwenden, um den Kontext an einen neuen Speicherort zu kopieren (natürlich nachdem Speicher dafür zugewiesen wurde).Datenspeicher
Jetzt brauchen wir einen Platz zum Aufbewahren des Rahmens:AVFrame *pFrame = NULL;
pFrame=av_frame_alloc();
Da wir PPM-Dateien ausgeben möchten, die in 24-Bit-RGB gespeichert sind, müssen wir unseren Frame von seinem eigenen Format in RGB konvertieren. FFmpeg wird es für uns tun. Für die meisten Projekte (einschließlich dieses) müssen Sie den Startrahmen in ein bestimmtes Format konvertieren. Wählen Sie einen Frame für den konvertierten Frame aus:
pFrameRGB=av_frame_alloc();
if(pFrameRGB==NULL)
return -1;
Trotz der Tatsache, dass wir den Frame ausgewählt haben, benötigen wir beim Konvertieren noch einen Platz, an dem die Rohdaten untergebracht werden können. Wir verwenden avpicture_get_size , um die richtigen Größen zu erhalten und den erforderlichen Speicherplatz manuell zuzuweisen:uint8_t *buffer = NULL;
int numBytes;
numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
pCodecCtx->height);
buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
av_malloc ist ein Analogon der C-Funktion malloc von FFmpeg, einem einfachen Wrapper um malloc , der die Ausrichtung von Speicheradressen usw. ermöglicht. Dies schützt übrigens nicht vor Speicherlecks, doppelter Freigabe oder anderen Problemen, die bei malloc auftreten .Jetzt verwenden wir avpicture_fill, um den Frame unserem neu zugewiesenen Puffer zuzuordnen. In Bezug auf AVPicture : Die AVPicture- Struktur ist eine Teilmenge der AVFrame- Struktur - der Anfang der AVFrame- Struktur ist identisch mit der AVPicture- Struktur .
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
pCodecCtx->width, pCodecCtx->height);
Wir sind schon am Ziel! Jetzt können wir aus dem Stream lesen!Daten lesen
Um nun den gesamten Videostream zu lesen, lesen wir das nächste Paket, entschlüsseln es in unserem Frame. Sobald die Entschlüsselung abgeschlossen ist, konvertieren wir den Frame und speichern ihn:struct SwsContext *sws_ctx = NULL;
int frameFinished;
AVPacket packet;
sws_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
PIX_FMT_RGB24,
SWS_BILINEAR,
NULL,
NULL,
NULL
);
i=0;
while(av_read_frame(pFormatCtx, &packet)>=0) {
if(packet.stream_index==videoStream) {
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
if(frameFinished) {
sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
pFrame->linesize, 0, pCodecCtx->height,
pFrameRGB->data, pFrameRGB->linesize);
if(++i<=5)
SaveFrame(pFrameRGB, pCodecCtx->width,
pCodecCtx->height, i);
}
}
av_free_packet(&packet);
}
Nichts kompliziertes: av_read_frame () liest das Paket und speichert es in der AVPacket- Struktur . Bitte beachten Sie, dass wir nur die Struktur des Pakets verteilen - FFmpeg liefert uns die internen Daten, auf die packet.data verweist . Dies gibt av_free_packet () etwas später frei . avcodec_decode_video () konvertiert das Paket in einen Frame. Möglicherweise verfügen wir jedoch nicht über alle Informationen, die wir nach dem Decodieren des Pakets für den Frame benötigen. Daher setzt avcodec_decode_video () frameFinished, wenn wir den nächsten Frame haben. Schließlich verwenden wir sws_scale (), um aus unserem eigenen Format ( pCodecCtx -> ) zu konvertierenpix_fmt ) in RGB. Denken Sie daran, dass Sie einen AVFrame- Zeiger in einen AVPicture- Zeiger umwandeln können . Schließlich übergeben wir die Informationen über den Rahmen, die Höhe und die Breite unserer SaveFrame- Funktion.Apropos Pakete. Technisch gesehen kann ein Paket nur einen Teil eines Rahmens sowie andere Datenbits enthalten. Der FFmpeg-Parser garantiert jedoch, dass die empfangenen Pakete entweder einen vollständigen Frame oder sogar mehrere Frames enthalten.Jetzt müssen Sie nur noch die SaveFrame- Funktion verwenden, um die RGB-Informationen in eine PPM-Datei zu schreiben. Obwohl wir uns oberflächlich mit dem PPM-Format selbst befassen; Glauben Sie mir, hier funktioniert alles:void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
FILE *pFile;
char szFilename[32];
int y;
sprintf(szFilename, "frame%d.ppm", iFrame);
pFile=fopen(szFilename, "wb");
if(pFile==NULL)
return;
fprintf(pFile, "P6\n%d %d\n255\n", width, height);
for(y=0; y<height; y++)
fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
fclose(pFile);
}
Wir führen ein Öffnen einer Standarddatei usw. durch und zeichnen dann die RGB-Daten auf. Die Datei wird Zeile für Zeile geschrieben. Eine PPM-Datei ist einfach eine Datei, in der RGB-Informationen als lange Zeile dargestellt werden. Wenn Sie die Farben von HTML kennen, ist dies so, als würden Sie die Farben jedes Pixels vom ersten bis zum letzten Ende markieren, etwa # ff0000 # ff0000 .... , wie bei einem roten Bildschirm. (Tatsächlich wird es im Binärformat und ohne Trennzeichen gespeichert, aber ich hoffe, Sie verstehen die Idee.) Der Titel gibt an, wie breit und hoch das Bild ist, sowie die maximale Größe der RGB-Werte.Nun zurück zu unserer main () Funktion . Sobald wir mit dem Lesen aus dem Videostream fertig sind, müssen wir nur noch alles löschen:
av_free(buffer);
av_free(pFrameRGB);
av_free(pFrame);
avcodec_close(pCodecCtx);
avcodec_close(pCodecCtxOrig);
avformat_close_input(&pFormatCtx);
return 0;
Wie Sie sehen können, verwenden wir av_free für den mit avcode_alloc_frame und av_malloc zugewiesenen Speicher .Das ist der ganze Code! Wenn Sie nun Linux oder eine ähnliche Plattform verwenden, führen Sie Folgendes aus:gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lavutil -lm
Wenn Sie eine ältere Version von FFmpeg haben, müssen Sie möglicherweise -lavutil entfernen :gcc -o tutorial01 tutorial01.c -lavformat -lavcodec -lz -lm
Die meisten Grafikprogramme müssen das PPM-Format öffnen. Schauen Sie sich einige Filmdateien an, deren Screencaps mit unserem Programm erstellt wurden.
Lektion 2: Anzeigen des Bildschirms ← ⇑ →
Vollständige Auflistung: tutorial02.c
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <SDL.h>
#include <SDL_thread.h>
#ifdef __MINGW32__
#undef main
#endif
#include <stdio.h>
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif
int main(int argc, char *argv[]) {
AVFormatContext *pFormatCtx = NULL;
int i, videoStream;
AVCodecContext *pCodecCtxOrig = NULL;
AVCodecContext *pCodecCtx = NULL;
AVCodec *pCodec = NULL;
AVFrame *pFrame = NULL;
AVPacket packet;
int frameFinished;
float aspect_ratio;
struct SwsContext *sws_ctx = NULL;
SDL_Overlay *bmp;
SDL_Surface *screen;
SDL_Rect rect;
SDL_Event event;
if(argc < 2) {
fprintf(stderr, "Usage: test <file>\n");
exit(1);
}
av_register_all();
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
exit(1);
}
if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
return -1;
if(avformat_find_stream_info(pFormatCtx, NULL)<0)
return -1;
av_dump_format(pFormatCtx, 0, argv[1], 0);
videoStream=-1;
for(i=0; i<pFormatCtx->nb_streams; i++)
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
videoStream=i;
break;
}
if(videoStream==-1)
return -1;
pCodecCtxOrig=pFormatCtx->streams[videoStream]->codec;
pCodec=avcodec_find_decoder(pCodecCtxOrig->codec_id);
if(pCodec==NULL) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
pCodecCtx = avcodec_alloc_context3(pCodec);
if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1;
}
if(avcodec_open2(pCodecCtx, pCodec, NULL)<0)
return -1;
pFrame=av_frame_alloc();
#ifndef __DARWIN__
screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
#else
screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0);
#endif
if(!screen) {
fprintf(stderr, "SDL: could not set video mode - exiting\n");
exit(1);
}
bmp = SDL_CreateYUVOverlay(pCodecCtx->width,
pCodecCtx->height,
SDL_YV12_OVERLAY,
screen);
sws_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
PIX_FMT_YUV420P,
SWS_BILINEAR,
NULL,
NULL,
NULL
);
i=0;
while(av_read_frame(pFormatCtx, &packet)>=0) {
if(packet.stream_index==videoStream) {
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
if(frameFinished) {
SDL_LockYUVOverlay(bmp);
AVPicture pict;
pict.data[0] = bmp->pixels[0];
pict.data[1] = bmp->pixels[2];
pict.data[2] = bmp->pixels[1];
pict.linesize[0] = bmp->pitches[0];
pict.linesize[1] = bmp->pitches[2];
pict.linesize[2] = bmp->pitches[1];
sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
pFrame->linesize, 0, pCodecCtx->height,
pict.data, pict.linesize);
SDL_UnlockYUVOverlay(bmp);
rect.x = 0;
rect.y = 0;
rect.w = pCodecCtx->width;
rect.h = pCodecCtx->height;
SDL_DisplayYUVOverlay(bmp, &rect);
}
}
av_free_packet(&packet);
SDL_PollEvent(&event);
switch(event.type) {
case SDL_QUIT:
SDL_Quit();
exit(0);
break;
default:
break;
}
}
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avcodec_close(pCodecCtxOrig);
avformat_close_input(&pFormatCtx);
return 0;
}
SDL und Video
Zum Zeichnen auf dem Bildschirm verwenden wir SDL. SDL steht für Simple Direct Layer . Es ist eine hervorragende plattformübergreifende Multimedia-Bibliothek, die in vielen Projekten verwendet wird. Sie können die Bibliothek auf der offiziellen Website herunterladen oder gegebenenfalls das Entwicklerpaket für Ihr Betriebssystem herunterladen. Sie benötigen Bibliotheken, um den Code aus dieser Lektion zu kompilieren (alle anderen Lektionen gelten übrigens auch).SDL bietet viele Methoden zum Zeichnen auf dem Bildschirm. Eine Möglichkeit, Filme anzuzeigen, ist das sogenannte YUV-Overlay .Formal nicht einmal YUV, sondern YCbCr. Einige Leute werden übrigens sehr verbrannt, wenn "YCbCr" als "YUV" bezeichnet wird. Im Allgemeinen ist YUV ein analoges Format und YCbCr ist ein digitales Format. FFmpeg und SDL bezeichnen in ihrem Code und in Makros YCbCr als YUV, aber das ist.YUV ist eine Methode zum Speichern von Rohbilddaten wie RGB. Grob gesagt ist Y eine Komponente der Helligkeit , und U und V sind Komponenten der Farbe . (Dies ist komplizierter als RGB, da ein Teil der Farbinformationen verworfen wird und Sie nur 1 Messung von U und V für jeweils 2 Messungen von Y durchführen können. ) YUV-Overlayin SDL akzeptiert einen rohen YUV-Datensatz und zeigt ihn an. Es werden 4 verschiedene Arten von YUV-Formaten akzeptiert, aber YV12 ist das schnellste davon. Es gibt ein anderes YUV-Format namens YUV420P , das mit YV12 übereinstimmt, außer dass die Arrays von U und V vertauscht werden. 420 bedeutet, dass es in einem Verhältnis von 4: 2: 0 abgetastet wird, dh für jeweils 4 Helligkeitsmessungen gibt es 1 Farbmessung, sodass die Farbinformationen in Vierteln verteilt werden. Dies ist ein guter Weg, um Bandbreite zu sparen, da das menschliche Auge diese Änderungen immer noch nicht bemerkt. Der lateinische Buchstabe "P" im Namen zeigt an, dass das Format "planar" ist, was einfach bedeutet, dass die Komponenten Y sind.U und V befinden sich in getrennten Arrays. FFmpeg kann Bilder in YUV420P konvertieren , was sehr hilfreich ist, da viele Videostreams bereits in diesem Format gespeichert sind oder einfach in dieses konvertiert werden können.Daher ist es derzeit geplant, die Funktion SaveFrame () aus der vorherigen Lektion zu ersetzen und stattdessen unseren Frame anzuzeigen. Zunächst müssen Sie sich jedoch mit den Grundfunktionen der SDL-Bibliothek vertraut machen. Verbinden Sie zunächst die Bibliotheken und initialisieren Sie die SDL:#include <SDL.h>
#include <SDL_thread.h>
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
exit(1);
}
SDL_Init () teilt der Bibliothek im Wesentlichen mit, welche Funktionen wir verwenden werden. SDL_GetError (), dies ist natürlich unsere praktische Funktion zum Debuggen.Anzeigeerstellung
Jetzt brauchen wir einen Platz auf dem Bildschirm, um die Elemente anzuordnen. Der Hauptbereich für die Anzeige von Bildern mit SDL wird als Oberfläche bezeichnet :SDL_Surface *screen;
screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
if(!screen) {
fprintf(stderr, "SDL: could not set video mode - exiting\n");
exit(1);
}
Also haben wir einen Bildschirm mit einer bestimmten Breite und Höhe eingerichtet. Die nächste Option ist die Bittiefe des Bildschirms - 0 - dies ist ein spezieller Wert, der "dasselbe wie die aktuelle Anzeige" bedeutet.Jetzt erstellen wir ein YUV-Overlay auf diesem Bildschirm, damit wir Videos darauf ausgeben können, und konfigurieren unseren SWSContext so, dass Bilddaten in YUV420 konvertiert werden :SDL_Overlay *bmp = NULL;
struct SWSContext *sws_ctx = NULL;
bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,
SDL_YV12_OVERLAY, screen);
sws_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
PIX_FMT_YUV420P,
SWS_BILINEAR,
NULL,
NULL,
NULL
);
Wie bereits erwähnt, verwenden wir YV12 , um das Bild anzuzeigen und die YUV420- Daten von FFmpeg abzurufen .Bildschirm
Das war einfach genug! Jetzt müssen wir nur noch das Bild zeigen. Gehen wir den ganzen Weg zu dem Ort, an dem wir den fertigen Schuss hatten. Wir können alles loswerden, was wir für den RGB-Frame hatten, und wir werden SaveFrame () durch unseren Anzeigecode ersetzen . Um das Bild anzuzeigen, erstellen wir eine AVPicture- Struktur und legen die Datenzeiger und die Liniengröße für unser YUV-Overlay fest : if(frameFinished) {
SDL_LockYUVOverlay(bmp);
AVPicture pict;
pict.data[0] = bmp->pixels[0];
pict.data[1] = bmp->pixels[2];
pict.data[2] = bmp->pixels[1];
pict.linesize[0] = bmp->pitches[0];
pict.linesize[1] = bmp->pitches[2];
pict.linesize[2] = bmp->pitches[1];
sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
pFrame->linesize, 0, pCodecCtx->height,
pict.data, pict.linesize);
SDL_UnlockYUVOverlay(bmp);
Zuerst blockieren wir das Overlay, weil wir vorhaben, darauf zu schreiben. Dies ist eine gute Angewohnheit, damit später keine Probleme auftreten. Die AVPicture- Struktur hat , wie oben gezeigt, einen Datenzeiger, der ein Array von 4 Zeigern ist. Da es sich hier um den YUV420P handelt , haben wir nur 3 Kanäle und damit nur 3 Datensätze. Andere Formate haben möglicherweise einen vierten Zeiger für den Alphakanal oder etwas anderes. Die Zeilengröße sieht so aus. Ähnliche Strukturen in unserer YUV-Überlagerung sind Variablen für Pixel und Höhen. (Tonhöhen, Tonhöhen - wenn in SDL ausgedrückt, um die Breite einer bestimmten Datenzeile anzugeben.) Wir geben also drei Bilddaten-Arrays in unserer Überlagerung an, also wenn wir schreibenIm Bild nehmen wir tatsächlich in unserem Overlay auf, dem natürlich bereits der dafür erforderliche Speicherplatz zugewiesen ist. Auf die gleiche Weise erhalten wir Informationen zur Zeilengröße direkt aus unserer Überlagerung. Wir ändern das Konvertierungsformat in PIX_FMT_YUV420P und verwenden sws_scale wie zuvor.Bildzeichnung
Wir müssen jedoch noch die SDL angeben, damit sie wirklich die Daten anzeigt, die wir ihr zur Verfügung gestellt haben. Wir übergeben dieser Funktion auch ein Rechteck, das angibt, wohin der Film gehen soll, auf welche Breite und Höhe er skaliert werden soll. Daher skaliert die SDL für uns, und dies kann Ihrer GPU helfen, schneller zu skalieren:SDL_Rect rect;
if(frameFinished) {
sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
pFrame->linesize, 0, pCodecCtx->height,
pict.data, pict.linesize);
SDL_UnlockYUVOverlay(bmp);
rect.x = 0;
rect.y = 0;
rect.w = pCodecCtx->width;
rect.h = pCodecCtx->height;
SDL_DisplayYUVOverlay(bmp, &rect);
}
Jetzt wird unser Video angezeigt!Lassen Sie uns eine weitere Funktion von SDL zeigen: das Ereignissystem . SDL ist so konfiguriert, dass beim Eingeben oder Bewegen der Maus in der SDL-Anwendung oder beim Senden eines Signals ein Ereignis generiert wird. Ihr Programm prüft dann auf diese Ereignisse, ob es Benutzereingaben verarbeiten soll. Ihr Programm kann auch Ereignisse erstellen, um SDL-Ereignisse an das System zu senden. Dies ist besonders nützlich für die Multithread-Programmierung mit SDL, die wir in Lektion 4 sehen werden. In unserem Programm werden wir Ereignisse unmittelbar nach der Verarbeitung des Pakets überprüfen. Im Moment werden wir das SDL_QUIT- Ereignis behandeln, damit wir beenden können:SDL_Event event;
av_free_packet(&packet);
SDL_PollEvent(&event);
switch(event.type) {
case SDL_QUIT:
SDL_Quit();
exit(0);
break;
default:
break;
}
Und so leben wir! Wir werden den ganzen alten Müll los und sind bereit zu kompilieren. Wenn Sie Linux oder ähnliches verwenden, können Sie am besten mit den SDL-Bibliotheken kompilieren:gcc -o tutorial02 tutorial02.c -lavformat -lavcodec -lswscale -lz -lm \
`sdl-config --cflags --libs`
sdl-config zeigt einfach die notwendigen Flags an, damit gcc die SDL-Bibliotheken korrekt aktiviert. Möglicherweise müssen Sie etwas anderes tun, um diese Kompilierung auf Ihrem System durchzuführen. Bitte überprüfen Sie die SDL-Dokumentation für Ihr System für jeden Feuerwehrmann. Nach dem Kompilieren fortfahren und ausführen.Was passiert, wenn Sie dieses Programm ausführen? Das Video scheint verrückt zu werden! Tatsächlich zeigen wir einfach alle Videobilder so schnell an, wie wir sie aus der Filmdatei extrahieren können. Wir haben derzeit nicht den Code, um herauszufinden, wann wir das Video zeigen müssen. Am Ende (in Lektion 5) werden wir beginnen, das Video zu synchronisieren. Aber im Moment fehlt uns etwas ebenso Wichtiges: der Sound!
Vollständige Auflistung: tutorial03.c
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <SDL.h>
#include <SDL_thread.h>
#ifdef __MINGW32__
#undef main
#endif
#include <stdio.h>
#include <assert.h>
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif
#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000
typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets;
int size;
SDL_mutex *mutex;
SDL_cond *cond;
} PacketQueue;
PacketQueue audioq;
int quit = 0;
void packet_queue_init(PacketQueue *q) {
memset(q, 0, sizeof(PacketQueue));
q->mutex = SDL_CreateMutex();
q->cond = SDL_CreateCond();
}
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
AVPacketList *pkt1;
if(av_dup_packet(pkt) < 0) {
return -1;
}
pkt1 = av_malloc(sizeof(AVPacketList));
if (!pkt1)
return -1;
pkt1->pkt = *pkt;
pkt1->next = NULL;
SDL_LockMutex(q->mutex);
if (!q->last_pkt)
q->first_pkt = pkt1;
else
q->last_pkt->next = pkt1;
q->last_pkt = pkt1;
q->nb_packets++;
q->size += pkt1->pkt.size;
SDL_CondSignal(q->cond);
SDL_UnlockMutex(q->mutex);
return 0;
}
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
AVPacketList *pkt1;
int ret;
SDL_LockMutex(q->mutex);
for(;;) {
if(quit) {
ret = -1;
break;
}
pkt1 = q->first_pkt;
if (pkt1) {
q->first_pkt = pkt1->next;
if (!q->first_pkt)
q->last_pkt = NULL;
q->nb_packets--;
q->size -= pkt1->pkt.size;
*pkt = pkt1->pkt;
av_free(pkt1);
ret = 1;
break;
} else if (!block) {
ret = 0;
break;
} else {
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex);
return ret;
}
int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf, int buf_size) {
static AVPacket pkt;
static uint8_t *audio_pkt_data = NULL;
static int audio_pkt_size = 0;
static AVFrame frame;
int len1, data_size = 0;
for(;;) {
while(audio_pkt_size > 0) {
int got_frame = 0;
len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);
if(len1 < 0) {
audio_pkt_size = 0;
break;
}
audio_pkt_data += len1;
audio_pkt_size -= len1;
data_size = 0;
if(got_frame) {
data_size = av_samples_get_buffer_size(NULL,
aCodecCtx->channels,
frame.nb_samples,
aCodecCtx->sample_fmt,
1);
assert(data_size <= buf_size);
memcpy(audio_buf, frame.data[0], data_size);
}
if(data_size <= 0) {
continue;
}
return data_size;
}
if(pkt.data)
av_free_packet(&pkt);
if(quit) {
return -1;
}
if(packet_queue_get(&audioq, &pkt, 1) < 0) {
return -1;
}
audio_pkt_data = pkt.data;
audio_pkt_size = pkt.size;
}
}
void audio_callback(void *userdata, Uint8 *stream, int len) {
AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
int len1, audio_size;
static uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2];
static unsigned int audio_buf_size = 0;
static unsigned int audio_buf_index = 0;
while(len > 0) {
if(audio_buf_index >= audio_buf_size) {
audio_size = audio_decode_frame(aCodecCtx, audio_buf, sizeof(audio_buf));
if(audio_size < 0) {
audio_buf_size = 1024;
memset(audio_buf, 0, audio_buf_size);
} else {
audio_buf_size = audio_size;
}
audio_buf_index = 0;
}
len1 = audio_buf_size - audio_buf_index;
if(len1 > len)
len1 = len;
memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
len -= len1;
stream += len1;
audio_buf_index += len1;
}
}
int main(int argc, char *argv[]) {
AVFormatContext *pFormatCtx = NULL;
int i, videoStream, audioStream;
AVCodecContext *pCodecCtxOrig = NULL;
AVCodecContext *pCodecCtx = NULL;
AVCodec *pCodec = NULL;
AVFrame *pFrame = NULL;
AVPacket packet;
int frameFinished;
struct SwsContext *sws_ctx = NULL;
AVCodecContext *aCodecCtxOrig = NULL;
AVCodecContext *aCodecCtx = NULL;
AVCodec *aCodec = NULL;
SDL_Overlay *bmp;
SDL_Surface *screen;
SDL_Rect rect;
SDL_Event event;
SDL_AudioSpec wanted_spec, spec;
if(argc < 2) {
fprintf(stderr, "Usage: test <file>\n");
exit(1);
}
av_register_all();
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
exit(1);
}
if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
return -1;
if(avformat_find_stream_info(pFormatCtx, NULL)<0)
return -1;
av_dump_format(pFormatCtx, 0, argv[1], 0);
videoStream=-1;
audioStream=-1;
for(i=0; i<pFormatCtx->nb_streams; i++) {
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO &&
videoStream < 0) {
videoStream=i;
}
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO &&
audioStream < 0) {
audioStream=i;
}
}
if(videoStream==-1)
return -1;
if(audioStream==-1)
return -1;
aCodecCtxOrig=pFormatCtx->streams[audioStream]->codec;
aCodec = avcodec_find_decoder(aCodecCtxOrig->codec_id);
if(!aCodec) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
aCodecCtx = avcodec_alloc_context3(aCodec);
if(avcodec_copy_context(aCodecCtx, aCodecCtxOrig) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1;
}
wanted_spec.freq = aCodecCtx->sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = aCodecCtx->channels;
wanted_spec.silence = 0;
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
wanted_spec.callback = audio_callback;
wanted_spec.userdata = aCodecCtx;
if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
return -1;
}
avcodec_open2(aCodecCtx, aCodec, NULL);
packet_queue_init(&audioq);
SDL_PauseAudio(0);
pCodecCtxOrig=pFormatCtx->streams[videoStream]->codec;
pCodec=avcodec_find_decoder(pCodecCtxOrig->codec_id);
if(pCodec==NULL) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
pCodecCtx = avcodec_alloc_context3(pCodec);
if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1;
}
if(avcodec_open2(pCodecCtx, pCodec, NULL)<0)
return -1;
pFrame=av_frame_alloc();
#ifndef __DARWIN__
screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
#else
screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0);
#endif
if(!screen) {
fprintf(stderr, "SDL: could not set video mode - exiting\n");
exit(1);
}
bmp = SDL_CreateYUVOverlay(pCodecCtx->width,
pCodecCtx->height,
SDL_YV12_OVERLAY,
screen);
sws_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
PIX_FMT_YUV420P,
SWS_BILINEAR,
NULL,
NULL,
NULL
);
i=0;
while(av_read_frame(pFormatCtx, &packet)>=0) {
if(packet.stream_index==videoStream) {
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
if(frameFinished) {
SDL_LockYUVOverlay(bmp);
AVPicture pict;
pict.data[0] = bmp->pixels[0];
pict.data[1] = bmp->pixels[2];
pict.data[2] = bmp->pixels[1];
pict.linesize[0] = bmp->pitches[0];
pict.linesize[1] = bmp->pitches[2];
pict.linesize[2] = bmp->pitches[1];
sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
pFrame->linesize, 0, pCodecCtx->height,
pict.data, pict.linesize);
SDL_UnlockYUVOverlay(bmp);
rect.x = 0;
rect.y = 0;
rect.w = pCodecCtx->width;
rect.h = pCodecCtx->height;
SDL_DisplayYUVOverlay(bmp, &rect);
av_free_packet(&packet);
}
} else if(packet.stream_index==audioStream) {
packet_queue_put(&audioq, &packet);
} else {
av_free_packet(&packet);
}
SDL_PollEvent(&event);
switch(event.type) {
case SDL_QUIT:
quit = 1;
SDL_Quit();
exit(0);
break;
default:
break;
}
}
av_frame_free(&pFrame);
avcodec_close(pCodecCtxOrig);
avcodec_close(pCodecCtx);
avcodec_close(aCodecCtxOrig);
avcodec_close(aCodecCtx);
avformat_close_input(&pFormatCtx);
return 0;
}
Audio
Jetzt möchten wir, dass der Sound in der Anwendung abgespielt wird. SDL bietet uns auch Methoden zum Abspielen von Sound. Mit der Funktion SDL_OpenAudio () wird das Audiogerät selbst geöffnet. Als Argumente wird die SDL_AudioSpec- Struktur verwendet , die alle Informationen zu dem Audio enthält, das abgespielt werden soll.Bevor wir zeigen, wie dies konfiguriert wird, erklären wir zunächst, wie der Computer Audio im Allgemeinen verarbeitet. Digitales Audio besteht aus einem langen Strom von Samples, von denen jede eine bestimmte Bedeutung einer Schallwelle darstellt. Sounds werden mit einer bestimmten Abtastrate aufgenommen, die lediglich angibt, wie schnell jedes Sample abgespielt wird, und anhand der Anzahl der Samples pro Sekunde gemessen. Die ungefähren Abtastfrequenzen betragen 22.050 und 44.100 Abtastfrequenzen pro Sekunde. Dies sind die Geschwindigkeiten, die für Radio bzw. CD verwendet werden. Darüber hinaus können die meisten Audiodaten mehr als einen Kanal für Stereo- oder Surround-Sound haben. Wenn sich das Sample beispielsweise in Stereo befindet, werden zwei Samples gleichzeitig ausgegeben. Wenn wir die Daten aus der Filmdatei erhalten, wissen wir nicht, wie viele Samples wir erhalten werden, aber FFmpeg erzeugt keine defekten Samples - dies bedeutet auch, dass das Stereo-Sample nicht getrennt wird.Die Methode zum Abspielen von Audio in SDL ist wie folgt. Die Soundparameter sind konfiguriert: Abtastfrequenz, Anzahl der Kanäle usw. Stellen Sie außerdem die Rückruffunktion und die Benutzerdaten ein. Wenn wir mit der Tonwiedergabe beginnen, ruft die SDL diese Rückruffunktion ständig auf und fordert sie auf, den Audiopuffer mit einer bestimmten Anzahl von Bytes zu füllen. Nachdem wir diese Informationen in die SDL_AudioSpec- Struktur eingefügt haben , rufen wir SDL_OpenAudio () auf, wodurch das Audiogerät geöffnet und eine weitere AudioSpec- Struktur zurückgegeben wird . Dies sind die Eigenschaften, die wir tatsächlich verwenden werden - es gibt keine Garantie dafür, dass wir genau das bekommen, wonach wir gefragt haben!Audioeinstellung
Denken Sie vorerst daran, denn wir haben noch keine Informationen zu Audio-Streams! Kehren wir zu der Stelle in unserem Code zurück, an der wir den Videostream gefunden haben, und finden Sie heraus, welcher Stream der Audiostream ist:
videoStream=-1;
audioStream=-1;
for(i=0; i < pFormatCtx->nb_streams; i++) {
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO
&&
videoStream < 0) {
videoStream=i;
}
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO &&
audioStream < 0) {
audioStream=i;
}
}
if(videoStream==-1)
return -1;
if(audioStream==-1)
return -1;
Hier können wir alle gewünschten Informationen aus dem AVCodecContext aus dem Stream abrufen , genau wie wir es mit dem Videostream getan haben:AVCodecContext *aCodecCtxOrig;
AVCodecContext *aCodecCtx;
aCodecCtxOrig=pFormatCtx->streams[audioStream]->codec;
Wenn Sie sich erinnern, müssen wir in früheren Lektionen den Audio-Codec selbst noch öffnen. Das ist einfach:AVCodec *aCodec;
aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
if(!aCodec) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
aCodecCtx = avcodec_alloc_context3(aCodec);
if(avcodec_copy_context(aCodecCtx, aCodecCtxOrig) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1;
}
avcodec_open2(aCodecCtx, aCodec, NULL);
Im Kontext des Codecs enthält alle Informationen, die zur Konfiguration unseres Audios erforderlich sind:wanted_spec.freq = aCodecCtx->sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = aCodecCtx->channels;
wanted_spec.silence = 0;
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
wanted_spec.callback = audio_callback;
wanted_spec.userdata = aCodecCtx;
if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
return -1;
}
Lassen Sie uns jeden Punkt durchgehen:- Frequenz (Frequenz): Abtastrate, wie zuvor erläutert.
- format (): SDL , . «S» «S16SYS» «», 16 , 16 , «SYS» , , . , avcodec_decode_audio2 .
- channels (): .
- silence (): , . 0.
- samples (): , , SDL , . - 512 8192; FFplay, , 1024.
- Rückruf (Rückruf): Hier übergeben wir die eigentliche Rückruffunktion. Wir werden später mehr über die Rückruffunktion sprechen.
- Benutzerdaten : Die SDL gibt unserem Rückruf einen Nullzeiger auf alle gewünschten Benutzerdaten. Wir möchten ihn über unseren Codec-Kontext informieren. etwas tiefer wird klar sein warum.
Öffnen Sie zum Schluss das Audio mit SDL_OpenAudio .Warteschlangen
Und es ist notwendig! Jetzt können wir Audioinformationen aus dem Stream extrahieren. Aber was tun mit diesen Informationen? Wir werden kontinuierlich Pakete aus der Filmdatei empfangen, aber gleichzeitig ruft die SDL die Rückruffunktion auf! Die Lösung wird darin bestehen, eine Art globale Struktur zu erstellen, in die wir Audiopakete einfügen können, damit unser audio_callback etwas zum Empfangen von Audiodaten hat! Hier ist also, was wir tun werden, um die Paketwarteschlange zu erstellen. FFmpeg hat sogar eine Struktur, die dabei hilft: AVPacketList , eine verknüpfte Liste für Pakete. Hier ist unsere Warteschlangenstruktur:typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets;
int size;
SDL_mutex *mutex;
SDL_cond *cond;
} PacketQueue;
Zunächst müssen wir angeben, dass nb_packets unterschiedlich groß ist - die Größe bezieht sich auf die Größe des Bytes, das wir von package-> size erhalten . Beachten Sie, dass wir einen Mutex und eine Bedingungsvariable haben. Dies liegt daran, dass die SDL den Audioprozess als separaten Stream ausführt. Wenn wir die Warteschlange nicht richtig blockieren, können wir unsere Daten wirklich ruinieren. Mal sehen, wie die Warteschlange implementiert wird. Jeder Programmierer mit Selbstachtung sollte wissen, wie man Warteschlangen erstellt. Wir zeigen Ihnen jedoch auch, wie dies funktioniert, damit Sie die SDL-Funktionen leichter erlernen können.Zuerst erstellen wir eine Funktion zum Initialisieren der Warteschlange:void packet_queue_init(PacketQueue *q) {
memset(q, 0, sizeof(PacketQueue));
q->mutex = SDL_CreateMutex();
q->cond = SDL_CreateCond();
}
Erstellen Sie dann eine Funktion, um Objekte in unsere Warteschlange zu stellen:int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
AVPacketList *pkt1;
if(av_dup_packet(pkt) < 0) {
return -1;
}
pkt1 = av_malloc(sizeof(AVPacketList));
if (!pkt1)
return -1;
pkt1->pkt = *pkt;
pkt1->next = NULL;
SDL_LockMutex(q->mutex);
if (!q->last_pkt)
q->first_pkt = pkt1;
else
q->last_pkt->next = pkt1;
q->last_pkt = pkt1;
q->nb_packets++;
q->size += pkt1->pkt.size;
SDL_CondSignal(q->cond);
SDL_UnlockMutex(q->mutex);
return 0;
}
SDL_LockMutex () blockiert den Mutex in der Warteschlange, damit wir etwas hinzufügen können, und dann sendet SDL_CondSignal () ein Signal an unsere get- Funktion (falls erwartet) über unsere bedingte Variable, um ihr mitzuteilen, dass Daten vorhanden sind, und kann für weitere Zwecke fortgesetzt werden Mutex entsperren.Hier ist die entsprechende Get- Funktion . Beachten Sie, wie SDL_CondWait () den Funktionsblock erstellt (d. H. Pausiert, bis wir die Daten erhalten), wenn wir ihn dazu auffordern:int quit = 0;
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) {
AVPacketList *pkt1;
int ret;
SDL_LockMutex(q->mutex);
for(;;) {
if(quit) {
ret = -1;
break;
}
pkt1 = q->first_pkt;
if (pkt1) {
q->first_pkt = pkt1->next;
if (!q->first_pkt)
q->last_pkt = NULL;
q->nb_packets--;
q->size -= pkt1->pkt.size;
*pkt = pkt1->pkt;
av_free(pkt1);
ret = 1;
break;
} else if (!block) {
ret = 0;
break;
} else {
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex);
return ret;
}
Wie Sie sehen können, haben wir die Funktion in einen ewigen Zyklus eingeschlossen, sodass wir definitiv einige Daten erhalten, wenn wir sie blockieren möchten. Mit der Funktion SDL_CondWait () vermeiden wir eine Schleife für immer . Im Wesentlichen wartet CondWait lediglich auf ein Signal von SDL_CondSignal () (oder SDL_CondBroadcast ()) und fährt dann fort. Es sieht jedoch so aus, als hätten wir es in einem Mutex gefangen - wenn wir die Sperre gedrückt halten, kann unsere Put- Funktion nichts in die Warteschlange stellen! Was SDL_CondWait () jedoch auch für uns tut, ist, den Mutex, den wir ihm geben, zu entsperren und dann erneut zu versuchen, ihn zu sperren, sobald wir das Signal empfangen.Für jeden Feuerwehrmann
Sie sehen auch, dass wir eine globale Beendigungsvariable haben , die wir überprüfen, um sicherzustellen, dass wir das Ausgangssignal nicht im Programm eingestellt haben (SDL verarbeitet automatisch TERM- Signale usw.). Andernfalls wird der Thread für immer fortgesetzt und wir müssen das Programm mit kill -9 beenden : SDL_PollEvent(&event);
switch(event.type) {
case SDL_QUIT:
quit = 1;
Wir setzen das Exit-Flag auf 1.Wir füttern Pakete
Es bleibt nur die Konfiguration unserer Warteschlange:PacketQueue audioq;
main() {
...
avcodec_open2(aCodecCtx, aCodec, NULL);
packet_queue_init(&audioq);
SDL_PauseAudio(0);
SDL_PauseAudio () startet schließlich die Audioeinheit. Es reproduziert Stille, wenn es keine Daten empfängt. Dies geschieht jedoch nicht sofort.Wir haben also eine Warteschlange konfiguriert und können nun Pakete an sie senden. Wir fahren mit unserem Paketlesezyklus fort:while(av_read_frame(pFormatCtx, &packet)>=0) {
if(packet.stream_index==videoStream) {
....
}
} else if(packet.stream_index==audioStream) {
packet_queue_put(&audioq, &packet);
} else {
av_free_packet(&packet);
}
Bitte beachten Sie, dass wir das Paket nach dem Einreihen nicht freigeben. Wir werden es später veröffentlichen, wenn wir entschlüsseln.Pakete abrufen
Lassen Sie uns nun endlich unsere audio_callback- Funktion ausführen , um Pakete aus der Warteschlange abzurufen. Der Rückruf sollte folgendermaßen aussehen:void callback(void *userdata, Uint8 *stream, int len)
Benutzerdaten sind der Zeiger, den wir der SDL gegeben haben, Stream ist der Puffer, in den Audiodaten geschrieben werden, und len ist die Größe dieses Puffers. Hier ist der Code:void audio_callback(void *userdata, Uint8 *stream, int len) {
AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
int len1, audio_size;
static uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
static unsigned int audio_buf_size = 0;
static unsigned int audio_buf_index = 0;
while(len > 0) {
if(audio_buf_index >= audio_buf_size) {
audio_size = audio_decode_frame(aCodecCtx, audio_buf,
sizeof(audio_buf));
if(audio_size < 0) {
audio_buf_size = 1024;
memset(audio_buf, 0, audio_buf_size);
} else {
audio_buf_size = audio_size;
}
audio_buf_index = 0;
}
len1 = audio_buf_size - audio_buf_index;
if(len1 > len)
len1 = len;
memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
len -= len1;
stream += len1;
audio_buf_index += len1;
}
}
Tatsächlich ist dies eine einfache Schleife, die Daten aus einer anderen von uns geschriebenen Funktion, audio_decode_frame () , extrahiert , das Ergebnis in einem Zwischenpuffer speichert, versucht, len Bytes in den Stream zu schreiben und mehr Daten empfängt, wenn wir noch nicht genug haben oder sie für später speichern. wenn wir noch etwas übrig haben. Die Größe von audio_buf ist 1,5-mal so groß wie der größte Audio-Frame, den FFmpeg uns geben wird, was uns einen guten Spielraum gibt.Endgültige Audio-Entschlüsselung
Schauen wir uns die Innenseiten des audio_decode_frame- Decoders an :int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf,
int buf_size) {
static AVPacket pkt;
static uint8_t *audio_pkt_data = NULL;
static int audio_pkt_size = 0;
static AVFrame frame;
int len1, data_size = 0;
for(;;) {
while(audio_pkt_size > 0) {
int got_frame = 0;
len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);
if(len1 < 0) {
audio_pkt_size = 0;
break;
}
audio_pkt_data += len1;
audio_pkt_size -= len1;
data_size = 0;
if(got_frame) {
data_size = av_samples_get_buffer_size(NULL,
aCodecCtx->channels,
frame.nb_samples,
aCodecCtx->sample_fmt,
1);
assert(data_size <= buf_size);
memcpy(audio_buf, frame.data[0], data_size);
}
if(data_size <= 0) {
continue;
}
return data_size;
}
if(pkt.data)
av_free_packet(&pkt);
if(quit) {
return -1;
}
if(packet_queue_get(&audioq, &pkt, 1) < 0) {
return -1;
}
audio_pkt_data = pkt.data;
audio_pkt_size = pkt.size;
}
}
Der gesamte Prozess beginnt tatsächlich gegen Ende der Funktion, wo wir packet_queue_get () aufrufen . Wir nehmen das Paket aus der Warteschlange und speichern die Informationen daraus. Wenn das Paket funktioniert, rufen wir avcodec_decode_audio4 () auf, das der Schwesterfunktion avcodec_decode_video () sehr ähnlich ist , außer dass das Paket in diesem Fall mehr als einen Frame haben kann. Daher müssen Sie es möglicherweise mehrmals aufrufen, um alle Daten aus dem Paket abzurufen. Nachdem wir den Frame erhalten haben, kopieren wir ihn einfach in unseren Audiopuffer und stellen sicher, dass data_size kleiner als unser Audiopuffer ist . Denken Sie auch an das Casting von audio_bufauf den richtigen Typ, da SDL einen 8-Bit-Int-Puffer und FFmpeg Daten in einem 16-Bit-Int-Puffer liefert. Sie sollten auch den Unterschied zwischen len1 und data_size berücksichtigen . len1 ist die Größe des von uns verwendeten Pakets und data_size ist die Menge der zurückgegebenen Rohdaten.Wenn wir Daten haben, kehren wir sofort zurück, um herauszufinden, ob wir mehr Daten aus der Warteschlange abrufen müssen oder ob wir fertig sind. Wenn wir das Paket noch bearbeiten müssen, bleiben Sie dabei. Wenn Sie das Paket fertiggestellt haben, geben Sie es schließlich frei.Und das ist alles! Wir haben Audio von der Hauptleseschleife in die Warteschlange übertragen, die dann von der Funktion audio_callback gelesen wird, der diese Daten an die SDL und die SDL an Ihre Soundkarte überträgt. Gehen Sie voran und kompilieren Sie:gcc -o tutorial03 tutorial03.c -lavutil -lavformat -lavcodec -lswscale -lz -lm \
`sdl-config --cflags --libs`
Gip-gip-Hurra! Das Video wird immer noch mit maximaler Geschwindigkeit übertragen, aber der Ton wird bereits so abgespielt, wie er sollte. Warum so? Ja, da die Audioinformationen eine Abtastfrequenz haben - wir pumpen die Audioinformationen so schnell aus, wie sich herausstellt, aber das Audio wird in diesem Stream einfach entsprechend seiner Abtastfrequenz abgespielt.Wir sind fast reif für die Video- und Audiosynchronisation, aber zuerst müssen wir eine kleine Neuorganisation des Programms durchführen. Die Methode, Sound in die Warteschlange zu stellen und ihn mit einem separaten Stream abzuspielen, funktionierte sehr gut: Sie machte den Code übersichtlicher und modularer. Bevor wir mit der Synchronisierung von Video und Audio beginnen, müssen wir den Code vereinfachen. In der nächsten Serie werden wir Kontrollflüsse erzeugen!
Lektion 4: Mehrere Themen ← ⇑ →
Vollständige Auflistung tutorial04.c
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <SDL.h>
#include <SDL_thread.h>
#ifdef __MINGW32__
#undef main
#endif
#include <stdio.h>
#include <assert.h>
#include <math.h>
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif
#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000
#define MAX_AUDIOQ_SIZE (5 * 16 * 1024)
#define MAX_VIDEOQ_SIZE (5 * 256 * 1024)
#define FF_REFRESH_EVENT (SDL_USEREVENT)
#define FF_QUIT_EVENT (SDL_USEREVENT + 1)
#define VIDEO_PICTURE_QUEUE_SIZE 1
typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets;
int size;
SDL_mutex *mutex;
SDL_cond *cond;
} PacketQueue;
typedef struct VideoPicture {
SDL_Overlay *bmp;
int width, height;
int allocated;
} VideoPicture;
typedef struct VideoState {
AVFormatContext *pFormatCtx;
int videoStream, audioStream;
AVStream *audio_st;
AVCodecContext *audio_ctx;
PacketQueue audioq;
uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
unsigned int audio_buf_size;
unsigned int audio_buf_index;
AVFrame audio_frame;
AVPacket audio_pkt;
uint8_t *audio_pkt_data;
int audio_pkt_size;
AVStream *video_st;
AVCodecContext *video_ctx;
PacketQueue videoq;
struct SwsContext *sws_ctx;
VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE];
int pictq_size, pictq_rindex, pictq_windex;
SDL_mutex *pictq_mutex;
SDL_cond *pictq_cond;
SDL_Thread *parse_tid;
SDL_Thread *video_tid;
char filename[1024];
int quit;
} VideoState;
SDL_Surface *screen;
SDL_mutex *screen_mutex;
VideoState *global_video_state;
void packet_queue_init(PacketQueue *q) {
memset(q, 0, sizeof(PacketQueue));
q->mutex = SDL_CreateMutex();
q->cond = SDL_CreateCond();
}
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
AVPacketList *pkt1;
if(av_dup_packet(pkt) < 0) {
return -1;
}
pkt1 = av_malloc(sizeof(AVPacketList));
if (!pkt1)
return -1;
pkt1->pkt = *pkt;
pkt1->next = NULL;
SDL_LockMutex(q->mutex);
if (!q->last_pkt)
q->first_pkt = pkt1;
else
q->last_pkt->next = pkt1;
q->last_pkt = pkt1;
q->nb_packets++;
q->size += pkt1->pkt.size;
SDL_CondSignal(q->cond);
SDL_UnlockMutex(q->mutex);
return 0;
}
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
AVPacketList *pkt1;
int ret;
SDL_LockMutex(q->mutex);
for(;;) {
if(global_video_state->quit) {
ret = -1;
break;
}
pkt1 = q->first_pkt;
if (pkt1) {
q->first_pkt = pkt1->next;
if (!q->first_pkt)
q->last_pkt = NULL;
q->nb_packets--;
q->size -= pkt1->pkt.size;
*pkt = pkt1->pkt;
av_free(pkt1);
ret = 1;
break;
} else if (!block) {
ret = 0;
break;
} else {
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex);
return ret;
}
int audio_decode_frame(VideoState *is, uint8_t *audio_buf, int buf_size) {
int len1, data_size = 0;
AVPacket *pkt = &is->audio_pkt;
for(;;) {
while(is->audio_pkt_size > 0) {
int got_frame = 0;
len1 = avcodec_decode_audio4(is->audio_ctx, &is->audio_frame, &got_frame, pkt);
if(len1 < 0) {
is->audio_pkt_size = 0;
break;
}
data_size = 0;
if(got_frame) {
data_size = av_samples_get_buffer_size(NULL,
is->audio_ctx->channels,
is->audio_frame.nb_samples,
is->audio_ctx->sample_fmt,
1);
assert(data_size <= buf_size);
memcpy(audio_buf, is->audio_frame.data[0], data_size);
}
is->audio_pkt_data += len1;
is->audio_pkt_size -= len1;
if(data_size <= 0) {
continue;
}
return data_size;
}
if(pkt->data)
av_free_packet(pkt);
if(is->quit) {
return -1;
}
if(packet_queue_get(&is->audioq, pkt, 1) < 0) {
return -1;
}
is->audio_pkt_data = pkt->data;
is->audio_pkt_size = pkt->size;
}
}
void audio_callback(void *userdata, Uint8 *stream, int len) {
VideoState *is = (VideoState *)userdata;
int len1, audio_size;
while(len > 0) {
if(is->audio_buf_index >= is->audio_buf_size) {
audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf));
if(audio_size < 0) {
is->audio_buf_size = 1024;
memset(is->audio_buf, 0, is->audio_buf_size);
} else {
is->audio_buf_size = audio_size;
}
is->audio_buf_index = 0;
}
len1 = is->audio_buf_size - is->audio_buf_index;
if(len1 > len)
len1 = len;
memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
len -= len1;
stream += len1;
is->audio_buf_index += len1;
}
}
static Uint32 sdl_refresh_timer_cb(Uint32 interval, void *opaque) {
SDL_Event event;
event.type = FF_REFRESH_EVENT;
event.user.data1 = opaque;
SDL_PushEvent(&event);
return 0;
}
static void schedule_refresh(VideoState *is, int delay) {
SDL_AddTimer(delay, sdl_refresh_timer_cb, is);
}
void video_display(VideoState *is) {
SDL_Rect rect;
VideoPicture *vp;
float aspect_ratio;
int w, h, x, y;
int i;
vp = &is->pictq[is->pictq_rindex];
if(vp->bmp) {
if(is->video_ctx->sample_aspect_ratio.num == 0) {
aspect_ratio = 0;
} else {
aspect_ratio = av_q2d(is->video_ctx->sample_aspect_ratio) *
is->video_ctx->width / is->video_ctx->height;
}
if(aspect_ratio <= 0.0) {
aspect_ratio = (float)is->video_ctx->width /
(float)is->video_ctx->height;
}
h = screen->h;
w = ((int)rint(h * aspect_ratio)) & -3;
if(w > screen->w) {
w = screen->w;
h = ((int)rint(w / aspect_ratio)) & -3;
}
x = (screen->w - w) / 2;
y = (screen->h - h) / 2;
rect.x = x;
rect.y = y;
rect.w = w;
rect.h = h;
SDL_LockMutex(screen_mutex);
SDL_DisplayYUVOverlay(vp->bmp, &rect);
SDL_UnlockMutex(screen_mutex);
}
}
void video_refresh_timer(void *userdata) {
VideoState *is = (VideoState *)userdata;
VideoPicture *vp;
if(is->video_st) {
if(is->pictq_size == 0) {
schedule_refresh(is, 1);
} else {
vp = &is->pictq[is->pictq_rindex];
schedule_refresh(is, 40);
video_display(is);
if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_rindex = 0;
}
SDL_LockMutex(is->pictq_mutex);
is->pictq_size--;
SDL_CondSignal(is->pictq_cond);
SDL_UnlockMutex(is->pictq_mutex);
}
} else {
schedule_refresh(is, 100);
}
}
void alloc_picture(void *userdata) {
VideoState *is = (VideoState *)userdata;
VideoPicture *vp;
vp = &is->pictq[is->pictq_windex];
if(vp->bmp) {
SDL_FreeYUVOverlay(vp->bmp);
}
SDL_LockMutex(screen_mutex);
vp->bmp = SDL_CreateYUVOverlay(is->video_ctx->width,
is->video_ctx->height,
SDL_YV12_OVERLAY,
screen);
SDL_UnlockMutex(screen_mutex);
vp->width = is->video_ctx->width;
vp->height = is->video_ctx->height;
vp->allocated = 1;
}
int queue_picture(VideoState *is, AVFrame *pFrame) {
VideoPicture *vp;
int dst_pix_fmt;
AVPicture pict;
SDL_LockMutex(is->pictq_mutex);
while(is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE &&
!is->quit) {
SDL_CondWait(is->pictq_cond, is->pictq_mutex);
}
SDL_UnlockMutex(is->pictq_mutex);
if(is->quit)
return -1;
vp = &is->pictq[is->pictq_windex];
if(!vp->bmp ||
vp->width != is->video_ctx->width ||
vp->height != is->video_ctx->height) {
SDL_Event event;
vp->allocated = 0;
alloc_picture(is);
if(is->quit) {
return -1;
}
}
if(vp->bmp) {
SDL_LockYUVOverlay(vp->bmp);
dst_pix_fmt = PIX_FMT_YUV420P;
pict.data[0] = vp->bmp->pixels[0];
pict.data[1] = vp->bmp->pixels[2];
pict.data[2] = vp->bmp->pixels[1];
pict.linesize[0] = vp->bmp->pitches[0];
pict.linesize[1] = vp->bmp->pitches[2];
pict.linesize[2] = vp->bmp->pitches[1];
sws_scale(is->sws_ctx, (uint8_t const * const *)pFrame->data,
pFrame->linesize, 0, is->video_ctx->height,
pict.data, pict.linesize);
SDL_UnlockYUVOverlay(vp->bmp);
if(++is->pictq_windex == VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_windex = 0;
}
SDL_LockMutex(is->pictq_mutex);
is->pictq_size++;
SDL_UnlockMutex(is->pictq_mutex);
}
return 0;
}
int video_thread(void *arg) {
VideoState *is = (VideoState *)arg;
AVPacket pkt1, *packet = &pkt1;
int frameFinished;
AVFrame *pFrame;
pFrame = av_frame_alloc();
for(;;) {
if(packet_queue_get(&is->videoq, packet, 1) < 0) {
break;
}
avcodec_decode_video2(is->video_ctx, pFrame, &frameFinished, packet);
if(frameFinished) {
if(queue_picture(is, pFrame) < 0) {
break;
}
}
av_free_packet(packet);
}
av_frame_free(&pFrame);
return 0;
}
int stream_component_open(VideoState *is, int stream_index) {
AVFormatContext *pFormatCtx = is->pFormatCtx;
AVCodecContext *codecCtx = NULL;
AVCodec *codec = NULL;
SDL_AudioSpec wanted_spec, spec;
if(stream_index < 0 || stream_index >= pFormatCtx->nb_streams) {
return -1;
}
codec = avcodec_find_decoder(pFormatCtx->streams[stream_index]->codec->codec_id);
if(!codec) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
codecCtx = avcodec_alloc_context3(codec);
if(avcodec_copy_context(codecCtx, pFormatCtx->streams[stream_index]->codec) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1;
}
if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO) {
wanted_spec.freq = codecCtx->sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = codecCtx->channels;
wanted_spec.silence = 0;
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
wanted_spec.callback = audio_callback;
wanted_spec.userdata = is;
if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
return -1;
}
}
if(avcodec_open2(codecCtx, codec, NULL) < 0) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
switch(codecCtx->codec_type) {
case AVMEDIA_TYPE_AUDIO:
is->audioStream = stream_index;
is->audio_st = pFormatCtx->streams[stream_index];
is->audio_ctx = codecCtx;
is->audio_buf_size = 0;
is->audio_buf_index = 0;
memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));
packet_queue_init(&is->audioq);
SDL_PauseAudio(0);
break;
case AVMEDIA_TYPE_VIDEO:
is->videoStream = stream_index;
is->video_st = pFormatCtx->streams[stream_index];
is->video_ctx = codecCtx;
packet_queue_init(&is->videoq);
is->video_tid = SDL_CreateThread(video_thread, is);
is->sws_ctx = sws_getContext(is->video_ctx->width, is->video_ctx->height,
is->video_ctx->pix_fmt, is->video_ctx->width,
is->video_ctx->height, PIX_FMT_YUV420P,
SWS_BILINEAR, NULL, NULL, NULL
);
break;
default:
break;
}
}
int decode_thread(void *arg) {
VideoState *is = (VideoState *)arg;
AVFormatContext *pFormatCtx;
AVPacket pkt1, *packet = &pkt1;
int video_index = -1;
int audio_index = -1;
int i;
is->videoStream=-1;
is->audioStream=-1;
global_video_state = is;
if(avformat_open_input(&pFormatCtx, is->filename, NULL, NULL)!=0)
return -1;
is->pFormatCtx = pFormatCtx;
if(avformat_find_stream_info(pFormatCtx, NULL)<0)
return -1;
av_dump_format(pFormatCtx, 0, is->filename, 0);
for(i=0; i<pFormatCtx->nb_streams; i++) {
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO &&
video_index < 0) {
video_index=i;
}
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO &&
audio_index < 0) {
audio_index=i;
}
}
if(audio_index >= 0) {
stream_component_open(is, audio_index);
}
if(video_index >= 0) {
stream_component_open(is, video_index);
}
if(is->videoStream < 0 || is->audioStream < 0) {
fprintf(stderr, "%s: could not open codecs\n", is->filename);
goto fail;
}
for(;;) {
if(is->quit) {
break;
}
if(is->audioq.size > MAX_AUDIOQ_SIZE ||
is->videoq.size > MAX_VIDEOQ_SIZE) {
SDL_Delay(10);
continue;
}
if(av_read_frame(is->pFormatCtx, packet) < 0) {
if(is->pFormatCtx->pb->error == 0) {
SDL_Delay(100);
continue;
} else {
break;
}
}
if(packet->stream_index == is->videoStream) {
packet_queue_put(&is->videoq, packet);
} else if(packet->stream_index == is->audioStream) {
packet_queue_put(&is->audioq, packet);
} else {
av_free_packet(packet);
}
}
while(!is->quit) {
SDL_Delay(100);
}
fail:
if(1){
SDL_Event event;
event.type = FF_QUIT_EVENT;
event.user.data1 = is;
SDL_PushEvent(&event);
}
return 0;
}
int main(int argc, char *argv[]) {
SDL_Event event;
VideoState *is;
is = av_mallocz(sizeof(VideoState));
if(argc < 2) {
fprintf(stderr, "Usage: test <file>\n");
exit(1);
}
av_register_all();
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
exit(1);
}
#ifndef __DARWIN__
screen = SDL_SetVideoMode(640, 480, 0, 0);
#else
screen = SDL_SetVideoMode(640, 480, 24, 0);
#endif
if(!screen) {
fprintf(stderr, "SDL: could not set video mode - exiting\n");
exit(1);
}
screen_mutex = SDL_CreateMutex();
av_strlcpy(is->filename, argv[1], sizeof(is->filename));
is->pictq_mutex = SDL_CreateMutex();
is->pictq_cond = SDL_CreateCond();
schedule_refresh(is, 40);
is->parse_tid = SDL_CreateThread(decode_thread, is);
if(!is->parse_tid) {
av_free(is);
return -1;
}
for(;;) {
SDL_WaitEvent(&event);
switch(event.type) {
case FF_QUIT_EVENT:
case SDL_QUIT:
is->quit = 1;
SDL_Quit();
return 0;
break;
case FF_REFRESH_EVENT:
video_refresh_timer(event.user.data1);
break;
default:
break;
}
}
return 0;
}
Überblick
Beim letzten Mal haben wir Audio-Unterstützung mithilfe der SDL-Audiofunktionen hinzugefügt. SDL hat einen Thread gestartet, der Rückrufe für die Funktion erstellt, die wir jedes Mal definiert haben, wenn ein Sound benötigt wurde. Jetzt machen wir dasselbe mit der Videoanzeige. Dies macht den Code modularer und einfacher zu bearbeiten - insbesondere, wenn Sie die Synchronisierung hinzufügen möchten. Wo fangen wir an?Beachten Sie, dass unsere Hauptfunktion viel erledigt: Sie durchläuft die Ereignisschleife, liest Pakete und decodiert das Video. Was wir tun werden, ist, alles in Teile zu teilen: Wir werden einen Stream haben, der für das Decodieren von Paketen verantwortlich ist; Dann werden diese Pakete zur Warteschlange hinzugefügt und von den entsprechenden Audio- und Videostreams gelesen. Wir haben den Audiostream bereits nach Bedarf eingestellt. Mit einem Videostream wird es etwas schwieriger, da wir sicherstellen müssen, dass das Video selbst gezeigt wird. Wir werden den eigentlichen Anzeigecode zur Hauptschleife hinzufügen. Anstatt das Video jedes Mal anzuzeigen, wenn wir die Schleife ausführen, integrieren wir die Videoanzeige in die Ereignisschleife. Die Idee ist, das Video zu dekodieren, den empfangenen Frame in einer anderen Warteschlange zu speichern und dann ein eigenes Ereignis ( FF_REFRESH_EVENT) zu erstellen), die wir dem Ereignissystem hinzufügen. Wenn unsere Ereignisschleife dieses Ereignis sieht, wird der nächste Frame in der Warteschlange angezeigt. Hier ist eine praktische ASCII-Darstellung des Geschehens:Der Hauptgrund für das Verschieben der Videoanzeigesteuerung durch die Ereignisschleife besteht darin, dass wir mit dem SDL_Delay- Stream genau steuern können, wann das nächste Videobild auf dem Bildschirm angezeigt wird. Wenn wir das Video in der nächsten Lektion endlich synchronisieren, fügen Sie einfach einen Code hinzu, der das nächste Video-Update so plant, dass das richtige Bild zum richtigen Zeitpunkt auf dem Bildschirm angezeigt wird.Vereinfachen Sie den Code
Lassen Sie uns den Code ein wenig löschen. Wir haben all diese Informationen über Audio- und Video-Codecs und werden Warteschlangen, Puffer und Gott weiß was noch hinzufügen. All diese Dinge sind für eine bestimmte logische Einheit, nämlich - für den Film. Wir beabsichtigen daher, eine große Struktur mit all diesen Informationen namens VideoState zu erstellen .typedef struct VideoState {
AVFormatContext *pFormatCtx;
int videoStream, audioStream;
AVStream *audio_st;
AVCodecContext *audio_ctx;
PacketQueue audioq;
uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
unsigned int audio_buf_size;
unsigned int audio_buf_index;
AVPacket audio_pkt;
uint8_t *audio_pkt_data;
int audio_pkt_size;
AVStream *video_st;
AVCodecContext *video_ctx;
PacketQueue videoq;
VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE];
int pictq_size, pictq_rindex, pictq_windex;
SDL_mutex *pictq_mutex;
SDL_cond *pictq_cond;
SDL_Thread *parse_tid;
SDL_Thread *video_tid;
char filename[1024];
int quit;
} VideoState;
Hier sehen wir Hinweise darauf, was wir am Ende bekommen werden. Zunächst sehen wir die grundlegenden Informationen - den Kontext des Formats und die Indizes des Audio- und Videostreams sowie die entsprechenden AVStream- Objekte . Dann sehen wir, dass einige dieser Audiopuffer in diese Struktur verschoben werden. Sie ( audio_buf , audio_buf_size usw.) waren für Informationen zu Audio gedacht, das noch vorhanden war (oder fehlte). Wir haben eine weitere Warteschlange für Videos und einen Puffer (der als Warteschlange verwendet wird; dafür benötigen wir keine extravaganten Warteschlangen) für dekodierte Frames hinzugefügt (als Overlay gespeichert). VideoPicture- Struktur- Dies ist unsere eigene Schöpfung (wir werden sehen, was darin sein wird, wenn wir dazu kommen). Sie können auch feststellen, dass wir Zeiger für zwei zusätzliche Streams zugewiesen haben, die wir erstellen werden, sowie ein Exit-Flag und einen Filmdateinamen.Nun kehren wir zur Hauptfunktion zurück, um zu sehen, wie dies unser Programm ändert. Lassen Sie uns unsere VideoState- Struktur einrichten :int main(int argc, char *argv[]) {
SDL_Event event;
VideoState *is;
is = av_mallocz(sizeof(VideoState));
av_mallocz () ist eine gute Funktion, die Speicher für uns reserviert und auf Null setzt.Dann initialisieren wir unsere Sperren für den Anzeigepuffer ( pictq ), da die Anzeigeschleife vordecodierte Frames aus pictq abruft , da die Ereignisschleife unsere Anzeigefunktion aufruft . Gleichzeitig wird unser Videodecoder Informationen einfügen - wir wissen nicht, wer zuerst dort ankommt. Ich hoffe du verstehst, dass dies eine klassische Rennbedingung ist. Deshalb verteilen wir es jetzt, bevor wir mit Themen beginnen. Kopieren wir auch den Namen unseres Films in VideoState :av_strlcpy(is->filename, argv[1], sizeof(is->filename));
is->pictq_mutex = SDL_CreateMutex();
is->pictq_cond = SDL_CreateCond();
av_strlcpy ist eine Funktion von FFmpeg, die neben strncpy einige zusätzliche Randprüfungen durchführt .Unser erster Thread
Lassen Sie uns unsere Threads ausführen und etwas Reales tun:schedule_refresh(is, 40);
is->parse_tid = SDL_CreateThread(decode_thread, is);
if(!is->parse_tid) {
av_free(is);
return -1;
}
Schedule_Refresh ist eine Funktion, die wir später definieren werden. Sie weist das System an, FF_REFRESH_EVENT nach der angegebenen Anzahl von Millisekunden zu erzeugen . Dadurch wird die Videoaktualisierungsfunktion aufgerufen, wenn sie in der Ereigniswarteschlange angezeigt wird. Aber jetzt schauen wir uns SDL_CreateThread () an.SDL_CreateThread () macht genau das - es erzeugt einen neuen Thread, der vollen Zugriff auf den gesamten Speicher des ursprünglichen Prozesses hat, und startet den Thread, der von der Funktion ausgeführt wird, die wir ihm geben. Diese Funktion überträgt auch benutzerdefinierte Daten. In diesem Fall rufen wir decode_thread () auf und hängen unsere VideoState- Struktur an. In der ersten Hälfte der Funktion gibt es nichts Neues. Es öffnet lediglich die Datei und findet den Index der Audio- und Videostreams. Das einzige, was wir anders machen, ist, den Formatkontext in unserer großen Struktur beizubehalten. Nachdem wir unsere Stream-Indizes gefunden haben, rufen wir eine andere von uns definierte Funktion auf, stream_component_open (). Dies ist eine ziemlich natürliche Art der Trennung. Da wir viele ähnliche Dinge tun, um den Video- und Audio-Codec einzurichten, verwenden wir einen Code wieder, wodurch er zu einer Funktion wird. Stream_component_open-Funktion() Ist der Ort, an dem wir unseren Codec-Decoder entdecken, die Soundparameter konfigurieren, wichtige Informationen in unserer großen Struktur speichern und unsere Audio- und Videostreams starten. Hier fügen wir auch andere Parameter ein, z. B. die erzwungene Verwendung des Codecs anstelle seiner automatischen Erkennung usw. So:int stream_component_open(VideoState *is, int stream_index) {
AVFormatContext *pFormatCtx = is->pFormatCtx;
AVCodecContext *codecCtx;
AVCodec *codec;
SDL_AudioSpec wanted_spec, spec;
if(stream_index < 0 || stream_index >= pFormatCtx->nb_streams) {
return -1;
}
codec = avcodec_find_decoder(pFormatCtx->streams[stream_index]->codec->codec_id);
if(!codec) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
codecCtx = avcodec_alloc_context3(codec);
if(avcodec_copy_context(codecCtx, pFormatCtx->streams[stream_index]->codec) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1;
}
if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO) {
wanted_spec.freq = codecCtx->sample_rate;
wanted_spec.callback = audio_callback;
wanted_spec.userdata = is;
if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
return -1;
}
}
if(avcodec_open2(codecCtx, codec, NULL) < 0) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
switch(codecCtx->codec_type) {
case AVMEDIA_TYPE_AUDIO:
is->audioStream = stream_index;
is->audio_st = pFormatCtx->streams[stream_index];
is->audio_ctx = codecCtx;
is->audio_buf_size = 0;
is->audio_buf_index = 0;
memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));
packet_queue_init(&is->audioq);
SDL_PauseAudio(0);
break;
case AVMEDIA_TYPE_VIDEO:
is->videoStream = stream_index;
is->video_st = pFormatCtx->streams[stream_index];
is->video_ctx = codecCtx;
packet_queue_init(&is->videoq);
is->video_tid = SDL_CreateThread(video_thread, is);
is->sws_ctx = sws_getContext(is->video_st->codec->width, is->video_st->codec->height,
is->video_st->codec->pix_fmt, is->video_st->codec->width,
is->video_st->codec->height, PIX_FMT_YUV420P,
SWS_BILINEAR, NULL, NULL, NULL
);
break;
default:
break;
}
}
Dies ist fast das gleiche wie der Code, den wir zuvor hatten, außer dass er jetzt für Audio und Video verallgemeinert ist. Beachten Sie, dass wir anstelle von aCodecCtx unsere große Struktur als Benutzerdaten für unseren Audio-Rückruf konfiguriert haben. Wir haben die Streams auch selbst als audio_st und video_st gespeichert . Wir haben auch unsere Video-Warteschlange hinzugefügt und genau wie unsere Audio-Warteschlange eingerichtet. Unter dem Strich werden Video- und Audio-Streams ausgeführt. Diese Bits tun dies: SDL_PauseAudio(0);
break;
is->video_tid = SDL_CreateThread(video_thread, is);
Denken Sie an SDL_PauseAudio () aus der letzten Lektion. SDL_CreateThread () wird auf die gleiche Weise verwendet. Zurück zu unserer Funktion video_thread (). Kehrenwir vorher zur zweiten Hälfte unserer Funktion decode_thread () zurück. Im Wesentlichen ist es nur eine for-Schleife, die ein Paket liest und in die richtige Warteschlange stellt: for(;;) {
if(is->quit) {
break;
}
if(is->audioq.size > MAX_AUDIOQ_SIZE ||
is->videoq.size > MAX_VIDEOQ_SIZE) {
SDL_Delay(10);
continue;
}
if(av_read_frame(is->pFormatCtx, packet) < 0) {
if((is->pFormatCtx->pb->error) == 0) {
SDL_Delay(100);
continue;
} else {
break;
}
}
if(packet->stream_index == is->videoStream) {
packet_queue_put(&is->videoq, packet);
} else if(packet->stream_index == is->audioStream) {
packet_queue_put(&is->audioq, packet);
} else {
av_free_packet(packet);
}
}
Hier gibt es nichts wirklich Neues, außer dass wir jetzt eine maximale Größe für unsere Audio- und Video-Warteschlange haben und die Lesefehlerprüfung hinzugefügt haben. Der Formatkontext hat eine ByteIOContext- Struktur namens pb . ByteIOContext ist eine Struktur, in der im Wesentlichen alle Informationen zu Dateien auf niedriger Ebene gespeichert werden.Nach unserer for-Schleife haben wir den gesamten Code, um zu warten, bis der Rest des Programms abgeschlossen ist, oder um darüber zu informieren. Dieser Code ist aufschlussreich, da er zeigt, wie wir Ereignisse pushen - etwas, das wir später benötigen, um das Video anzuzeigen: while(!is->quit) {
SDL_Delay(100);
}
fail:
if(1){
SDL_Event event;
event.type = FF_QUIT_EVENT;
event.user.data1 = is;
SDL_PushEvent(&event);
}
return 0;
Wir erhalten Werte für benutzerdefinierte Ereignisse mit der SDL-Konstante SDL_USEREVENT . Das erste Benutzerereignis muss auf SDL_USEREVENT , das nächste SDL_USEREVENT + 1 usw. gesetzt sein. FF_QUIT_EVENT ist in unserem Programm als SDL_USEREVENT + 1 definiert . Bei Bedarf können wir auch Benutzerdaten übergeben, und hier übergeben wir unseren Zeiger auf eine große Struktur. Schließlich rufen wir SDL_PushEvent () auf. In unserem Ereignisschleifenschalter haben wir dies einfach in den Abschnitt SDL_QUIT_EVENT eingefügtdas hatten wir vorher. Wir werden unseren Zyklus der Ereignisse detaillierter sehen; Stellen Sie vorerst sicher, dass wir FF_QUIT_EVENT später abfangen und das Exit-Flag wechseln.Bild empfangen: video_thread
Nachdem Sie den Codec vorbereitet haben, können Sie den Videostream starten. Dieser Stream liest Pakete aus der Video-Warteschlange, decodiert das Video in Frames und ruft dann die Funktion queue_picture auf, um den verarbeiteten Frame in die Bildwarteschlange zu stellen:int video_thread(void *arg) {
VideoState *is = (VideoState *)arg;
AVPacket pkt1, *packet = &pkt1;
int frameFinished;
AVFrame *pFrame;
pFrame = av_frame_alloc();
for(;;) {
if(packet_queue_get(&is->videoq, packet, 1) < 0) {
break;
}
avcodec_decode_video2(is->video_st->codec, pFrame, &frameFinished, packet);
if(frameFinished) {
if(queue_picture(is, pFrame) < 0) {
break;
}
}
av_free_packet(packet);
}
av_free(pFrame);
return 0;
}
Der größte Teil dieser Funktion sollte inzwischen verstanden werden. Wir haben gerade die Funktion avcodec_decode_video2 hier kopiert und einfach einige Argumente ersetzt. Zum Beispiel haben wir einen AVStream in unserer großen Struktur gespeichert, sodass wir unseren Codec von dort erhalten. Wir empfangen nur weiterhin Pakete aus unserer Video-Warteschlange, bis uns jemand zum Beenden auffordert oder wir einen Fehler finden.Warteschlangenrahmen
Werfen wir einen Blick auf die Funktion, mit der unser dekodierter pFrame in unserer Bildwarteschlange gespeichert wird . Da es sich bei unserer Bildwarteschlange um eine Überlagerung von SDL handelt (vermutlich, damit die Videoanzeigefunktion so wenig Berechnungen wie möglich durchführen kann), müssen wir unseren Frame in diese konvertieren. Die Daten, die wir in der Bildwarteschlange speichern, sind die Struktur, die wir erstellt haben:typedef struct VideoPicture {
SDL_Overlay *bmp;
int width, height;
int allocated;
} VideoPicture;
Unsere große Struktur enthält einen Puffer dieser Dateien, in dem wir sie speichern können. Wir müssen SDL_Overlay jedoch selbst verteilen (achten Sie auf das zugewiesene Flag, das anzeigt, ob wir es getan haben oder nicht).Um diese Warteschlange zu verwenden, haben wir zwei Zeiger - den Schreibindex und den Leseindex. Wir verfolgen auch, wie viele tatsächliche Bilder sich im Puffer befinden. Um in die Warteschlange zu schreiben, warten wir zuerst, bis unser Puffer gelöscht ist, damit wir einen Platz zum Speichern unseres VideoPicture haben . Dann prüfen wir, ob wir das Overlay in unserem Datensatzindex festgelegt haben. Wenn nicht, müssen Sie Speicher zuweisen. Wir müssen auch den Puffer neu zuweisen, wenn sich die Fenstergröße geändert hat!int queue_picture(VideoState *is, AVFrame *pFrame) {
VideoPicture *vp;
int dst_pix_fmt;
AVPicture pict;
SDL_LockMutex(is->pictq_mutex);
while(is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE &&
!is->quit) {
SDL_CondWait(is->pictq_cond, is->pictq_mutex);
}
SDL_UnlockMutex(is->pictq_mutex);
if(is->quit)
return -1;
vp = &is->pictq[is->pictq_windex];
if(!vp->bmp ||
vp->width != is->video_st->codec->width ||
vp->height != is->video_st->codec->height) {
SDL_Event event;
vp->allocated = 0;
alloc_picture(is);
if(is->quit) {
return -1;
}
}
Schauen wir uns die Funktion alloc_picture () an:void alloc_picture(void *userdata) {
VideoState *is = (VideoState *)userdata;
VideoPicture *vp;
vp = &is->pictq[is->pictq_windex];
if(vp->bmp) {
SDL_FreeYUVOverlay(vp->bmp);
}
SDL_LockMutex(screen_mutex);
vp->bmp = SDL_CreateYUVOverlay(is->video_st->codec->width,
is->video_st->codec->height,
SDL_YV12_OVERLAY,
screen);
SDL_UnlockMutex(screen_mutex);
vp->width = is->video_st->codec->width;
vp->height = is->video_st->codec->height;
vp->allocated = 1;
}
Sie sollten die Funktion SDL_CreateYUVOverlay erkennen , die wir von unserer Hauptschleife in diesen Abschnitt verschoben haben. Dieser Code sollte inzwischen einigermaßen klar sein. Jetzt haben wir jedoch eine Mutex-Sperre, da zwei Threads nicht gleichzeitig Informationen auf den Bildschirm schreiben können! Dadurch kann unsere Funktion alloc_picture keine andere Funktion stören, die das Bild anzeigt. (Wir haben diese Sperre als globale Variable erstellt und in main () initialisiert . Siehe Code.) Beachten Sie, dass wir die Breite und Höhe in der VideoPicture- Struktur beibehalten , da wir sicherstellen müssen, dass sich die Größe unseres Videos aus irgendeinem Grund nicht ändert.Ok, wir haben es geregelt und wir haben unser Overlay YUV, engagiert und bereit, das Bild zu empfangen. Kehren wir zu queue_picture zurück und sehen uns den Code an, um den Frame in das Overlay zu kopieren. Dieser Teil sollte Ihnen vertraut sein:int queue_picture(VideoState *is, AVFrame *pFrame) {
if(vp->bmp) {
SDL_LockYUVOverlay(vp->bmp);
dst_pix_fmt = PIX_FMT_YUV420P;
pict.data[0] = vp->bmp->pixels[0];
pict.data[1] = vp->bmp->pixels[2];
pict.data[2] = vp->bmp->pixels[1];
pict.linesize[0] = vp->bmp->pitches[0];
pict.linesize[1] = vp->bmp->pitches[2];
pict.linesize[2] = vp->bmp->pitches[1];
sws_scale(is->sws_ctx, (uint8_t const * const *)pFrame->data,
pFrame->linesize, 0, is->video_st->codec->height,
pict.data, pict.linesize);
SDL_UnlockYUVOverlay(vp->bmp);
if(++is->pictq_windex == VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_windex = 0;
}
SDL_LockMutex(is->pictq_mutex);
is->pictq_size++;
SDL_UnlockMutex(is->pictq_mutex);
}
return 0;
}
Hier ist das meiste nur der Code, den wir zuvor verwendet haben, um das YUV- Overlay mit unserem Frame zu füllen . Das letzte Bit "fügt" einfach unseren Wert zur Warteschlange hinzu. Die Warteschlange funktioniert, Werte werden hinzugefügt, bis sie voll ist, und das Lesen erfolgt, solange sich mindestens etwas darin befindet. Daher hängt alles vom Wert von is -> pictq_size ab , weshalb wir ihn blockieren müssen. Also, was machen wir hier: Erhöhen Sie den Datensatzzeiger (und beginnen Sie gegebenenfalls von vorne), blockieren Sie dann die Warteschlange und vergrößern Sie sie. Jetzt wird unser Leser wissen, dass es mehr Informationen über die Warteschlange gibt, und wenn dies unsere Warteschlange voll macht, wird unser Rekorder davon erfahren.Videoanzeige
Das ist alles für unseren Videothread! Jetzt haben wir alle freien Threads bis auf einen abgeschlossen - erinnern Sie sich, wie wir die Funktion sched_refresh () vor langer Zeit aufgerufen haben ? Schauen Sie sich an, was tatsächlich passiert ist:
static void schedule_refresh(VideoState *is, int delay) {
SDL_AddTimer(delay, sdl_refresh_timer_cb, is);
}
SDL_AddTimer () ist eine SDL-Funktion, die nach einer bestimmten Anzahl von Millisekunden einfach einen Rückruf an eine benutzerdefinierte Funktion ausführt (und bei Bedarf einige benutzerdefinierte Daten überträgt). Wir werden diese Funktion verwenden, um Videoaktualisierungen zu planen. Jedes Mal, wenn wir sie aufrufen, wird ein Timer festgelegt, der ein Ereignis auslöst, wodurch unsere main () - Funktion eine Funktion aufruft, die einen Frame aus unserem Warteschlangenbild extrahiert und anzeigt ihr! Puh! Drei "welche / welche / welche" in einem Satz! Lassen Sie uns also als Erstes dieses Ereignis auslösen. Dies schickt uns zu:static Uint32 sdl_refresh_timer_cb(Uint32 interval, void *opaque) {
SDL_Event event;
event.type = FF_REFRESH_EVENT;
event.user.data1 = opaque;
SDL_PushEvent(&event);
return 0;
}
Die Veranstaltung wird von unserem alten Freund ins Leben gerufen. FF_REFRESH_EVENT ist hier als SDL_USEREVENT + 1 definiert . Es ist zu beachten, dass die SDL den Timer stoppt, wenn wir 0 zurückgeben, sodass der Rückruf nicht erneut ausgeführt wird. Nachdemwir FF_REFRESH_EVENT erneut aufgerufen haben , müssen wir es in unserer Ereignisschleife verarbeiten:for(;;) {
SDL_WaitEvent(&event);
switch(event.type) {
case FF_REFRESH_EVENT:
video_refresh_timer(event.user.data1);
break;
Was schickt uns hierher zu dieser Funktion, die tatsächlich Daten aus unserer Bildwarteschlange extrahiert:void video_refresh_timer(void *userdata) {
VideoState *is = (VideoState *)userdata;
VideoPicture *vp;
if(is->video_st) {
if(is->pictq_size == 0) {
schedule_refresh(is, 1);
} else {
vp = &is->pictq[is->pictq_rindex];
schedule_refresh(is, 80);
video_display(is);
if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_rindex = 0;
}
SDL_LockMutex(is->pictq_mutex);
is->pictq_size--;
SDL_CondSignal(is->pictq_cond);
SDL_UnlockMutex(is->pictq_mutex);
}
} else {
schedule_refresh(is, 100);
}
}
Im Moment ist diese Funktion ziemlich einfach: Sie verarbeitet die Warteschlange, während wir etwas haben, stellt einen Timer für die Anzeige des nächsten Videobilds ein, ruft video_display auf, um das Video tatsächlich auf dem Bildschirm anzuzeigen, erhöht dann den Zähler in der Warteschlange und verringert gleichzeitig seine Größe. Sie werden vielleicht bemerken, dass wir in dieser Funktion nicht wirklich etwas mit vp machen , und hier ist der Grund: Dies liegt vor uns. Aber etwas später. Wir werden es verwenden, um auf Zeitinformationen zuzugreifen, wenn wir beginnen, Video mit Audio zu synchronisieren. Schauen Sie sich hier die Stelle im Code an, an der der Kommentar „Timing-Code geht hierher“ geschrieben ist. In diesem Abschnitt werden wir , wie schnell um herauszufinden wir den nächsten Video - Frame anzeigen sollen, und dann diesen Wert in der Eingabe schedule_refresh Funktion(). Im Moment geben wir nur einen fiktiven Wert von 80 ein. Technisch gesehen können Sie diesen Wert erraten und überprüfen und für jeden Film neu kompilieren, aber: 1) er wird nach einer Weile langsamer und 2) er ist ziemlich dumm. Obwohl wir in Zukunft auf diesen Punkt zurückkommen werden.Wir sind fast fertig. Es bleibt nur noch eines zu tun: Zeigen Sie das Video! Hier ist die Funktion video_display :void video_display(VideoState *is) {
SDL_Rect rect;
VideoPicture *vp;
float aspect_ratio;
int w, h, x, y;
int i;
vp = &is->pictq[is->pictq_rindex];
if(vp->bmp) {
if(is->video_st->codec->sample_aspect_ratio.num == 0) {
aspect_ratio = 0;
} else {
aspect_ratio = av_q2d(is->video_st->codec->sample_aspect_ratio) *
is->video_st->codec->width / is->video_st->codec->height;
}
if(aspect_ratio <= 0.0) {
aspect_ratio = (float)is->video_st->codec->width /
(float)is->video_st->codec->height;
}
h = screen->h;
w = ((int)rint(h * aspect_ratio)) & -3;
if(w > screen->w) {
w = screen->w;
h = ((int)rint(w / aspect_ratio)) & -3;
}
x = (screen->w - w) / 2;
y = (screen->h - h) / 2;
rect.x = x;
rect.y = y;
rect.w = w;
rect.h = h;
SDL_LockMutex(screen_mutex);
SDL_DisplayYUVOverlay(vp->bmp, &rect);
SDL_UnlockMutex(screen_mutex);
}
}
Da der Bildschirm eine beliebige Größe haben kann (wir haben 640 x 480 installiert und es gibt Möglichkeiten, ihn so anzupassen, dass die Größe des Benutzers geändert wird), müssen Sie dynamisch bestimmen, wie groß der rechteckige Bereich für unseren Film sein soll. Zuerst müssen Sie das Seitenverhältnis unseres Films herausfinden, nur die Breite geteilt durch die Höhe. Einige Codecs haben ein ungerades Seitenverhältnis des Samples, dh einfach die Breite / Höhe eines Pixels oder Samples. Da die Höhen- und Breitenwerte in unserem Codec-Kontext in Pixel gemessen werden, entspricht das tatsächliche Seitenverhältnis dem Seitenverhältnis multipliziert mit dem Seitenverhältnis für die Stichprobe. Einige Codecs zeigen ein Seitenverhältnis von 0, was bedeutet, dass jedes Pixel einfach eine Größe von 1x1 hat. Dann skalieren wir den Film sodamit es so gut wie möglich auf den Bildschirm passt. Bitumkehr& -3 rundet den Wert einfach auf das nächste Vielfache von vier. Zentrieren Sie dann den Film und rufen Sie SDL_DisplayYUVOverlay () auf, um sicherzustellen, dass der Bildschirmmutex für den Zugriff verwendet wird.Und das ist alles? Sind wir fertig? Sie müssen den Audiocode noch neu schreiben, um das neue VideoStruct verwenden zu können. Dies sind jedoch triviale Änderungen, die im Beispielcode zu sehen sind. Als letztes müssen wir unseren Rückruf für die interne Exit-Rückruffunktion in FFmpeg ändern:VideoState *global_video_state;
int decode_interrupt_cb(void) {
return (global_video_state && global_video_state->quit);
}
Setzen Sie global_video_state in main () auf eine große Struktur .So, das war es! Wir kompilieren:gcc -o tutorial04 tutorial04.c -lavutil -lavformat -lavcodec -lswscale -lz -lm \
`sdl-config --cflags --libs`
und genieße den Film ohne zu synchronisieren! Im nächsten Schritt werden wir endlich einen wirklich funktionierenden Videoplayer erstellen !
Vollständige Auflistung tutorial05.c
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <SDL.h>
#include <SDL_thread.h>
#ifdef __MINGW32__
#undef main
#endif
#include <stdio.h>
#include <assert.h>
#include <math.h>
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif
#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000
#define MAX_AUDIOQ_SIZE (5 * 16 * 1024)
#define MAX_VIDEOQ_SIZE (5 * 256 * 1024)
#define AV_SYNC_THRESHOLD 0.01
#define AV_NOSYNC_THRESHOLD 10.0
#define FF_REFRESH_EVENT (SDL_USEREVENT)
#define FF_QUIT_EVENT (SDL_USEREVENT + 1)
#define VIDEO_PICTURE_QUEUE_SIZE 1
typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets;
int size;
SDL_mutex *mutex;
SDL_cond *cond;
} PacketQueue;
typedef struct VideoPicture {
SDL_Overlay *bmp;
int width, height;
int allocated;
double pts;
} VideoPicture;
typedef struct VideoState {
AVFormatContext *pFormatCtx;
int videoStream, audioStream;
double audio_clock;
AVStream *audio_st;
AVCodecContext *audio_ctx;
PacketQueue audioq;
uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
unsigned int audio_buf_size;
unsigned int audio_buf_index;
AVFrame audio_frame;
AVPacket audio_pkt;
uint8_t *audio_pkt_data;
int audio_pkt_size;
int audio_hw_buf_size;
double frame_timer;
double frame_last_pts;
double frame_last_delay;
double video_clock;
AVStream *video_st;
AVCodecContext *video_ctx;
PacketQueue videoq;
struct SwsContext *sws_ctx;
VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE];
int pictq_size, pictq_rindex, pictq_windex;
SDL_mutex *pictq_mutex;
SDL_cond *pictq_cond;
SDL_Thread *parse_tid;
SDL_Thread *video_tid;
char filename[1024];
int quit;
} VideoState;
SDL_Surface *screen;
SDL_mutex *screen_mutex;
VideoState *global_video_state;
void packet_queue_init(PacketQueue *q) {
memset(q, 0, sizeof(PacketQueue));
q->mutex = SDL_CreateMutex();
q->cond = SDL_CreateCond();
}
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
AVPacketList *pkt1;
if(av_dup_packet(pkt) < 0) {
return -1;
}
pkt1 = av_malloc(sizeof(AVPacketList));
if (!pkt1)
return -1;
pkt1->pkt = *pkt;
pkt1->next = NULL;
SDL_LockMutex(q->mutex);
if (!q->last_pkt)
q->first_pkt = pkt1;
else
q->last_pkt->next = pkt1;
q->last_pkt = pkt1;
q->nb_packets++;
q->size += pkt1->pkt.size;
SDL_CondSignal(q->cond);
SDL_UnlockMutex(q->mutex);
return 0;
}
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
AVPacketList *pkt1;
int ret;
SDL_LockMutex(q->mutex);
for(;;) {
if(global_video_state->quit) {
ret = -1;
break;
}
pkt1 = q->first_pkt;
if (pkt1) {
q->first_pkt = pkt1->next;
if (!q->first_pkt)
q->last_pkt = NULL;
q->nb_packets--;
q->size -= pkt1->pkt.size;
*pkt = pkt1->pkt;
av_free(pkt1);
ret = 1;
break;
} else if (!block) {
ret = 0;
break;
} else {
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex);
return ret;
}
double get_audio_clock(VideoState *is) {
double pts;
int hw_buf_size, bytes_per_sec, n;
pts = is->audio_clock;
hw_buf_size = is->audio_buf_size - is->audio_buf_index;
bytes_per_sec = 0;
n = is->audio_ctx->channels * 2;
if(is->audio_st) {
bytes_per_sec = is->audio_ctx->sample_rate * n;
}
if(bytes_per_sec) {
pts -= (double)hw_buf_size / bytes_per_sec;
}
return pts;
}
int audio_decode_frame(VideoState *is, uint8_t *audio_buf, int buf_size, double *pts_ptr) {
int len1, data_size = 0;
AVPacket *pkt = &is->audio_pkt;
double pts;
int n;
for(;;) {
while(is->audio_pkt_size > 0) {
int got_frame = 0;
len1 = avcodec_decode_audio4(is->audio_ctx, &is->audio_frame, &got_frame, pkt);
if(len1 < 0) {
is->audio_pkt_size = 0;
break;
}
data_size = 0;
if(got_frame) {
data_size = av_samples_get_buffer_size(NULL,
is->audio_ctx->channels,
is->audio_frame.nb_samples,
is->audio_ctx->sample_fmt,
1);
assert(data_size <= buf_size);
memcpy(audio_buf, is->audio_frame.data[0], data_size);
}
is->audio_pkt_data += len1;
is->audio_pkt_size -= len1;
if(data_size <= 0) {
continue;
}
pts = is->audio_clock;
*pts_ptr = pts;
n = 2 * is->audio_ctx->channels;
is->audio_clock += (double)data_size /
(double)(n * is->audio_ctx->sample_rate);
return data_size;
}
if(pkt->data)
av_free_packet(pkt);
if(is->quit) {
return -1;
}
if(packet_queue_get(&is->audioq, pkt, 1) < 0) {
return -1;
}
is->audio_pkt_data = pkt->data;
is->audio_pkt_size = pkt->size;
if(pkt->pts != AV_NOPTS_VALUE) {
is->audio_clock = av_q2d(is->audio_st->time_base)*pkt->pts;
}
}
}
void audio_callback(void *userdata, Uint8 *stream, int len) {
VideoState *is = (VideoState *)userdata;
int len1, audio_size;
double pts;
while(len > 0) {
if(is->audio_buf_index >= is->audio_buf_size) {
audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf), &pts);
if(audio_size < 0) {
is->audio_buf_size = 1024;
memset(is->audio_buf, 0, is->audio_buf_size);
} else {
is->audio_buf_size = audio_size;
}
is->audio_buf_index = 0;
}
len1 = is->audio_buf_size - is->audio_buf_index;
if(len1 > len)
len1 = len;
memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
len -= len1;
stream += len1;
is->audio_buf_index += len1;
}
}
static Uint32 sdl_refresh_timer_cb(Uint32 interval, void *opaque) {
SDL_Event event;
event.type = FF_REFRESH_EVENT;
event.user.data1 = opaque;
SDL_PushEvent(&event);
return 0;
}
static void schedule_refresh(VideoState *is, int delay) {
SDL_AddTimer(delay, sdl_refresh_timer_cb, is);
}
void video_display(VideoState *is) {
SDL_Rect rect;
VideoPicture *vp;
float aspect_ratio;
int w, h, x, y;
int i;
vp = &is->pictq[is->pictq_rindex];
if(vp->bmp) {
if(is->video_ctx->sample_aspect_ratio.num == 0) {
aspect_ratio = 0;
} else {
aspect_ratio = av_q2d(is->video_ctx->sample_aspect_ratio) *
is->video_ctx->width / is->video_ctx->height;
}
if(aspect_ratio <= 0.0) {
aspect_ratio = (float)is->video_ctx->width /
(float)is->video_ctx->height;
}
h = screen->h;
w = ((int)rint(h * aspect_ratio)) & -3;
if(w > screen->w) {
w = screen->w;
h = ((int)rint(w / aspect_ratio)) & -3;
}
x = (screen->w - w) / 2;
y = (screen->h - h) / 2;
rect.x = x;
rect.y = y;
rect.w = w;
rect.h = h;
SDL_LockMutex(screen_mutex);
SDL_DisplayYUVOverlay(vp->bmp, &rect);
SDL_UnlockMutex(screen_mutex);
}
}
void video_refresh_timer(void *userdata) {
VideoState *is = (VideoState *)userdata;
VideoPicture *vp;
double actual_delay, delay, sync_threshold, ref_clock, diff;
if(is->video_st) {
if(is->pictq_size == 0) {
schedule_refresh(is, 1);
} else {
vp = &is->pictq[is->pictq_rindex];
delay = vp->pts - is->frame_last_pts;
if(delay <= 0 || delay >= 1.0) {
delay = is->frame_last_delay;
}
is->frame_last_delay = delay;
is->frame_last_pts = vp->pts;
ref_clock = get_audio_clock(is);
diff = vp->pts - ref_clock;
sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;
if(fabs(diff) < AV_NOSYNC_THRESHOLD) {
if(diff <= -sync_threshold) {
delay = 0;
} else if(diff >= sync_threshold) {
delay = 2 * delay;
}
}
is->frame_timer += delay;
actual_delay = is->frame_timer - (av_gettime() / 1000000.0);
if(actual_delay < 0.010) {
actual_delay = 0.010;
}
schedule_refresh(is, (int)(actual_delay * 1000 + 0.5));
video_display(is);
if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_rindex = 0;
}
SDL_LockMutex(is->pictq_mutex);
is->pictq_size--;
SDL_CondSignal(is->pictq_cond);
SDL_UnlockMutex(is->pictq_mutex);
}
} else {
schedule_refresh(is, 100);
}
}
void alloc_picture(void *userdata) {
VideoState *is = (VideoState *)userdata;
VideoPicture *vp;
vp = &is->pictq[is->pictq_windex];
if(vp->bmp) {
SDL_FreeYUVOverlay(vp->bmp);
}
SDL_LockMutex(screen_mutex);
vp->bmp = SDL_CreateYUVOverlay(is->video_ctx->width,
is->video_ctx->height,
SDL_YV12_OVERLAY,
screen);
SDL_UnlockMutex(screen_mutex);
vp->width = is->video_ctx->width;
vp->height = is->video_ctx->height;
vp->allocated = 1;
}
int queue_picture(VideoState *is, AVFrame *pFrame, double pts) {
VideoPicture *vp;
int dst_pix_fmt;
AVPicture pict;
SDL_LockMutex(is->pictq_mutex);
while(is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE &&
!is->quit) {
SDL_CondWait(is->pictq_cond, is->pictq_mutex);
}
SDL_UnlockMutex(is->pictq_mutex);
if(is->quit)
return -1;
vp = &is->pictq[is->pictq_windex];
if(!vp->bmp ||
vp->width != is->video_ctx->width ||
vp->height != is->video_ctx->height) {
SDL_Event event;
vp->allocated = 0;
alloc_picture(is);
if(is->quit) {
return -1;
}
}
if(vp->bmp) {
SDL_LockYUVOverlay(vp->bmp);
vp->pts = pts;
dst_pix_fmt = PIX_FMT_YUV420P;
pict.data[0] = vp->bmp->pixels[0];
pict.data[1] = vp->bmp->pixels[2];
pict.data[2] = vp->bmp->pixels[1];
pict.linesize[0] = vp->bmp->pitches[0];
pict.linesize[1] = vp->bmp->pitches[2];
pict.linesize[2] = vp->bmp->pitches[1];
sws_scale(is->sws_ctx, (uint8_t const * const *)pFrame->data,
pFrame->linesize, 0, is->video_ctx->height,
pict.data, pict.linesize);
SDL_UnlockYUVOverlay(vp->bmp);
if(++is->pictq_windex == VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_windex = 0;
}
SDL_LockMutex(is->pictq_mutex);
is->pictq_size++;
SDL_UnlockMutex(is->pictq_mutex);
}
return 0;
}
double synchronize_video(VideoState *is, AVFrame *src_frame, double pts) {
double frame_delay;
if(pts != 0) {
is->video_clock = pts;
} else {
pts = is->video_clock;
}
frame_delay = av_q2d(is->video_ctx->time_base);
frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
is->video_clock += frame_delay;
return pts;
}
int video_thread(void *arg) {
VideoState *is = (VideoState *)arg;
AVPacket pkt1, *packet = &pkt1;
int frameFinished;
AVFrame *pFrame;
double pts;
pFrame = av_frame_alloc();
for(;;) {
if(packet_queue_get(&is->videoq, packet, 1) < 0) {
break;
}
if(packet_queue_get(&is->videoq, packet, 1) < 0) {
break;
}
pts = 0;
avcodec_decode_video2(is->video_ctx, pFrame, &frameFinished, packet);
if((pts = av_frame_get_best_effort_timestamp(pFrame)) == AV_NOPTS_VALUE) {
pts = 0;
}
pts *= av_q2d(is->video_st->time_base);
if(frameFinished) {
pts = synchronize_video(is, pFrame, pts);
if(queue_picture(is, pFrame, pts) < 0) {
break;
}
}
av_free_packet(packet);
}
av_frame_free(&pFrame);
return 0;
}
int stream_component_open(VideoState *is, int stream_index) {
AVFormatContext *pFormatCtx = is->pFormatCtx;
AVCodecContext *codecCtx = NULL;
AVCodec *codec = NULL;
SDL_AudioSpec wanted_spec, spec;
if(stream_index < 0 || stream_index >= pFormatCtx->nb_streams) {
return -1;
}
codec = avcodec_find_decoder(pFormatCtx->streams[stream_index]->codec->codec_id);
if(!codec) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
codecCtx = avcodec_alloc_context3(codec);
if(avcodec_copy_context(codecCtx, pFormatCtx->streams[stream_index]->codec) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1;
}
if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO) {
wanted_spec.freq = codecCtx->sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = codecCtx->channels;
wanted_spec.silence = 0;
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
wanted_spec.callback = audio_callback;
wanted_spec.userdata = is;
if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
return -1;
}
is->audio_hw_buf_size = spec.size;
}
if(avcodec_open2(codecCtx, codec, NULL) < 0) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
switch(codecCtx->codec_type) {
case AVMEDIA_TYPE_AUDIO:
is->audioStream = stream_index;
is->audio_st = pFormatCtx->streams[stream_index];
is->audio_ctx = codecCtx;
is->audio_buf_size = 0;
is->audio_buf_index = 0;
memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));
packet_queue_init(&is->audioq);
SDL_PauseAudio(0);
break;
case AVMEDIA_TYPE_VIDEO:
is->videoStream = stream_index;
is->video_st = pFormatCtx->streams[stream_index];
is->video_ctx = codecCtx;
is->frame_timer = (double)av_gettime() / 1000000.0;
is->frame_last_delay = 40e-3;
packet_queue_init(&is->videoq);
is->video_tid = SDL_CreateThread(video_thread, is);
is->sws_ctx = sws_getContext(is->video_ctx->width, is->video_ctx->height,
is->video_ctx->pix_fmt, is->video_ctx->width,
is->video_ctx->height, PIX_FMT_YUV420P,
SWS_BILINEAR, NULL, NULL, NULL
);
break;
default:
break;
}
}
int decode_thread(void *arg) {
VideoState *is = (VideoState *)arg;
AVFormatContext *pFormatCtx;
AVPacket pkt1, *packet = &pkt1;
int video_index = -1;
int audio_index = -1;
int i;
is->videoStream=-1;
is->audioStream=-1;
global_video_state = is;
if(avformat_open_input(&pFormatCtx, is->filename, NULL, NULL)!=0)
return -1;
is->pFormatCtx = pFormatCtx;
if(avformat_find_stream_info(pFormatCtx, NULL)<0)
return -1;
av_dump_format(pFormatCtx, 0, is->filename, 0);
for(i=0; i<pFormatCtx->nb_streams; i++) {
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO &&
video_index < 0) {
video_index=i;
}
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO &&
audio_index < 0) {
audio_index=i;
}
}
if(audio_index >= 0) {
stream_component_open(is, audio_index);
}
if(video_index >= 0) {
stream_component_open(is, video_index);
}
if(is->videoStream < 0 || is->audioStream < 0) {
fprintf(stderr, "%s: could not open codecs\n", is->filename);
goto fail;
}
for(;;) {
if(is->quit) {
break;
}
if(is->audioq.size > MAX_AUDIOQ_SIZE ||
is->videoq.size > MAX_VIDEOQ_SIZE) {
SDL_Delay(10);
continue;
}
if(av_read_frame(is->pFormatCtx, packet) < 0) {
if(is->pFormatCtx->pb->error == 0) {
SDL_Delay(100);
continue;
} else {
break;
}
}
if(packet->stream_index == is->videoStream) {
packet_queue_put(&is->videoq, packet);
} else if(packet->stream_index == is->audioStream) {
packet_queue_put(&is->audioq, packet);
} else {
av_free_packet(packet);
}
}
while(!is->quit) {
SDL_Delay(100);
}
fail:
if(1){
SDL_Event event;
event.type = FF_QUIT_EVENT;
event.user.data1 = is;
SDL_PushEvent(&event);
}
return 0;
}
int main(int argc, char *argv[]) {
SDL_Event event;
VideoState *is;
is = av_mallocz(sizeof(VideoState));
if(argc < 2) {
fprintf(stderr, "Usage: test <file>\n");
exit(1);
}
av_register_all();
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
exit(1);
}
#ifndef __DARWIN__
screen = SDL_SetVideoMode(640, 480, 0, 0);
#else
screen = SDL_SetVideoMode(640, 480, 24, 0);
#endif
if(!screen) {
fprintf(stderr, "SDL: could not set video mode - exiting\n");
exit(1);
}
screen_mutex = SDL_CreateMutex();
av_strlcpy(is->filename, argv[1], sizeof(is->filename));
is->pictq_mutex = SDL_CreateMutex();
is->pictq_cond = SDL_CreateCond();
schedule_refresh(is, 40);
is->parse_tid = SDL_CreateThread(decode_thread, is);
if(!is->parse_tid) {
av_free(is);
return -1;
}
for(;;) {
SDL_WaitEvent(&event);
switch(event.type) {
case FF_QUIT_EVENT:
case SDL_QUIT:
is->quit = 1;
SDL_Quit();
return 0;
break;
case FF_REFRESH_EVENT:
video_refresh_timer(event.user.data1);
break;
default:
break;
}
}
return 0;
}
WARNUNG
Als ich gerade dieses Handbuch geschrieben habe, wurde mein gesamter Synchronisationscode aus der damaligen Version von ffplay.c übernommen . Heute ist es ein völlig anderes Programm, und Aktualisierungen in den FFmpeg-Bibliotheken (und in ffplay.c selbst) haben zu grundlegenden Änderungen geführt. Obwohl dieser Code immer noch funktioniert, ist er bereits veraltet und es gibt viele andere Verbesserungen, die in diesem Handbuch verwendet werden könnten.Wie Video synchronisiert
Bis jetzt hatten wir einen fast nutzlosen Movie Player. Ja, es wird Video abgespielt, und ja, es wird Audio abgespielt, aber das ist nicht ganz das, was wir einen Film nennen würden. Was machen wir dann?PTS und DTS
Glücklicherweise enthalten Audio- und Videostreams Informationen darüber, wie schnell und zu welchen Zeitpunkten sie abgespielt werden sollen. Audio-Streams haben eine Abtastrate und Video-Streams haben Bilder pro Sekunde. Wenn wir das Video jedoch nur synchronisieren, indem wir die Anzahl der Bilder zählen und mit der Bildrate multiplizieren, besteht eine gute Chance, dass es nicht mit dem Ton synchronisiert wird. Deshalb werden wir den anderen Weg gehen. Pakete aus dem Strom können die sogenannten haben Dekodierungszeitstempel ( DTS - von d ecoding t ime s Stampf ) und Präsentationszeitstempel ( PTS - aus p RÄSENTATION t ime sTamp ). Um diese beiden Bedeutungen zu verstehen, müssen Sie wissen, wie Filme gespeichert werden. Einige Formate, wie z. B. MPEG, verwenden sogenannte B-Frames ( Bed and is bidirektional, England. Bidirektional ). Zwei andere Arten von Frames werden als I-Frames und P-Frames ( I ist intern , i nner und P Mittel vorhergesagt , p redicted ). I-Frames enthalten das Vollbild. P-Frameshängen von vorherigen I- und P-Frames ab und unterscheiden sich von vorherigen Frames, oder Sie können auch - Deltas benennen. B-Frames ähneln P-Frames, hängen jedoch von den Informationen in vorherigen und nachfolgenden Frames ab! Die Tatsache, dass ein Frame möglicherweise nicht das Bild selbst enthält, sondern sich von anderen Frames unterscheidet, erklärt, warum wir nach dem Aufruf von avcodec_decode_video2 möglicherweise keinen fertigen Frame haben .Nehmen wir an, wir haben einen Film mit 4 Bildern in dieser Reihenfolge: IBBP . Dann müssen wir die Informationen aus dem letzten P-Frame herausfinden, bevor wir einen der beiden vorherigen B-Frames anzeigen können. Aus diesem Grund können Frames in einer Reihenfolge gespeichert werden, die nicht der tatsächlichen Anzeigereihenfolge entspricht: IPBB. Dafür sind der Dekodierungszeitstempel und der Präsentationszeitstempel für jeden Frame vorgesehen. Der Dekodierungszeitstempel sagt uns, wann wir etwas dekodieren müssen, und der Präsentationszeitstempel sagt uns, wann wir etwas anzeigen müssen. In diesem Fall sieht unser Stream also möglicherweise folgendermaßen aus: PTS: 1 4 2 3 DTS: 1 2 3 4Stream: IPBBIn der Regel unterscheiden sich PTS und DTS nur, wenn der abgespielte Stream B-Frames enthält.Wenn wir ein Paket von av_read_frame () erhalten, enthält es die PTS- und DTS-Werte für die Informationen, die sich im Paket befinden. Was wir aber wirklich brauchen, ist das PTS unseres neu dekodierten Rohrahmens. In diesem Fall wissen wir, wann es angezeigt werden muss.Glücklicherweise liefert uns FFmpeg den „bestmöglichen Zeitstempel“, den wir mit der Funktion av_frame_get_best_effort_timestamp () erhalten können .Synchronisation
Damit Frames der Reihe nach angezeigt werden, ist es schön zu wissen, wann ein bestimmtes Videobild angezeigt werden soll. Aber wie genau machen wir das? Die Idee ist folgende: Nachdem wir den Rahmen gezeigt haben, finden wir heraus, wann der nächste Rahmen gezeigt werden soll. Dann machen Sie einfach eine Pause, danach aktualisieren wir das Video nach dieser Zeit. Wie erwartet überprüfen wir den PTS-Wert des nächsten Frames auf der Systemuhr, um festzustellen, wie lange unsere Wartezeit sein sollte. Dieser Ansatz funktioniert, aber es gibt zwei Probleme, die angegangen werden müssen.Zunächst stellt sich die Frage, wann der nächste PTS sein wird. Sie werden sagen, dass Sie einfach die Videofrequenz zum aktuellen PTS hinzufügen können - und Sie haben im Prinzip Recht. Für einige Arten von Videos müssen jedoch Frames wiederholt werden. Dies bedeutet, dass Sie den aktuellen Frame einige Male wiederholen müssen. Dies kann dazu führen, dass das Programm das nächste Bild zu früh anzeigt. Dies muss berücksichtigt werden.Das zweite Problem ist, dass in dem Programm, das wir gerade geschrieben haben, Video und Audio freudig vorwärts eilen, bis sie sich überhaupt die Mühe machen, sich zu synchronisieren. Wir müssten uns keine Sorgen machen, wenn alles für sich perfekt funktionieren würde. Ihr Computer ist jedoch nicht perfekt, ebenso wie viele Videodateien. Daher haben wir drei Möglichkeiten: Audio mit Video synchronisieren, Video mit Audio synchronisieren oder Audio und Video mit einer externen Uhr synchronisieren (z. B. mit Ihrem Computer). Jetzt werden wir das Video mit Audio synchronisieren.Codierung: Empfang eines PTS-Frames
Jetzt schreiben wir etwas direkt. Wir müssen unserer großen Struktur noch ein paar Teile hinzufügen, und wir werden es so machen, wie wir es brauchen. Schauen wir uns zunächst unseren Videothread an. Denken Sie daran, dass wir hier Pakete sammeln, die von unserem Dekodierungsstrom in die Warteschlange gestellt wurden? In diesem Teil des Codes müssen wir den PTS für den Frame abrufen , den avcodec_decode_video2 uns gegeben hat . Der erste Weg, über den wir gesprochen haben, besteht darin, die DTS des zuletzt verarbeiteten Pakets zu erhalten, was ziemlich einfach ist: double pts;
for(;;) {
if(packet_queue_get(&is->videoq, packet, 1) < 0) {
break;
}
pts = 0;
len1 = avcodec_decode_video2(is->video_st->codec,
pFrame, &frameFinished, packet);
if(packet->dts != AV_NOPTS_VALUE) {
pts = av_frame_get_best_effort_timestamp(pFrame);
} else {
pts = 0;
}
pts *= av_q2d(is->video_st->time_base);
Wir setzen das PTS auf Null, wenn wir seinen Wert nicht bestimmen können.Das war einfach. Technischer Hinweis: Wie Sie sehen, verwenden wir int64 für PTS. Dies liegt daran, dass der PTS als Ganzzahl gespeichert ist. Dieser Wert ist ein Zeitstempel, der der Zeitdimension in timebb entspricht . Wenn der Stream beispielsweise 24 Frames pro Sekunde hat, gibt PTS von 42 an, dass der Frame dort verwendet werden soll, wo der 42. Frame sein soll, vorausgesetzt, wir haben Frames alle 1/24 Sekunde ersetzt (dies muss natürlich nicht so sein tatsächlich).Wir können diesen Wert in Sekunden umwandeln, indem wir ihn durch die Bildrate dividieren. Time_base WertDer Stream ist gleich 1 geteilt durch die Framerate (für Inhalte mit einer festen Framerate) . Um den PTS in Sekunden zu erhalten, multiplizieren wir ihn mit time_base .Code weiter: Synchronisation und Verwendung von PTS
Jetzt haben wir also alle fertigen PTS. Jetzt werden wir uns um die beiden Synchronisationsprobleme kümmern, die etwas höher diskutiert wurden. Wir werden eine Funktion synchronize_video definieren , die den PTS aktualisiert, um mit allem zu synchronisieren. Diese Funktion behandelt schließlich auch Fälle, in denen wir den PTS-Wert für unseren Frame nicht erhalten. Gleichzeitig müssen wir verfolgen, wann der nächste Frame erwartet wird, damit wir die Aktualisierungsrate korrekt einstellen können. Wir können dies mit dem internen video_clock- Wert tun , der verfolgt, wie viel Zeit für das Video vergangen ist. Diesen Wert fügen wir unserer großen Struktur hinzu:typedef struct VideoState {
double video_clock;
Hier ist die Funktion synchronize_video , die ziemlich klar ist:double synchronize_video(VideoState *is, AVFrame *src_frame, double pts) {
double frame_delay;
if(pts != 0) {
is->video_clock = pts;
} else {
pts = is->video_clock;
}
frame_delay = av_q2d(is->video_st->codec->time_base);
frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
is->video_clock += frame_delay;
return pts;
}
Wie Sie sehen können, berücksichtigen wir in dieser Funktion wiederholte Frames.Jetzt wollen wir unsere richtige PTS erhalten und den Rahmen Warteschlange mit queue_picture durch einen neuen Zusatz - Punkte Argument :
if(frameFinished) {
pts = synchronize_video(is, pFrame, pts);
if(queue_picture(is, pFrame, pts) < 0) {
break;
}
}
Das einzige , was Veränderungen in queue_picture ist , dass wir diese speichern pts Wert im Videobild Struktur , dass wir Warteschlange. Daher müssen wir die Variable pts zur Struktur hinzufügen und diese Codezeilen hinzufügen:typedef struct VideoPicture {
...
double pts;
}
int queue_picture(VideoState *is, AVFrame *pFrame, double pts) {
... stuff ...
if(vp->bmp) {
... convert picture ...
vp->pts = pts;
... alert queue ...
}
Jetzt haben wir die Bilder mit den richtigen PTS-Werten in die Warteschlange gestellt. Schauen wir uns also unsere Video-Update-Funktion an. Sie können sich aus der letzten Lektion daran erinnern, dass wir es einfach gefälscht und ein Update von 80 ms installiert haben. Nun werden wir herausfinden, was wirklich da sein sollte.Unsere Strategie ist es durch einfaches Messen der Zeit zwischen den aktuellen Zeit der nächsten PTS zur Vorhersage Punkte und die vorherigen. Gleichzeitig müssen wir das Video mit Audio synchronisieren. Wir werden eine Audiouhr machen .: Ein interner Wert, der die Position des abgespielten Audios verfolgt. Es ist wie eine digitale Anzeige auf jedem MP3-Player. Da wir das Video mit dem Ton synchronisieren, verwendet der Videostream diesen Wert, um herauszufinden, ob er zu weit vorne oder zu weit hinten liegt.Wir werden später zur Implementierung zurückkehren. Nehmen wir nun an, wir haben die Funktion get_audio_clockDas gibt uns Zeit auf der Audio-Uhr. Was ist zu tun, wenn Video und Audio nicht synchronisiert sind, sobald wir diesen Wert erhalten? Es wäre dumm, nur zu versuchen, durch eine Suche oder etwas anderes zum richtigen Paket zu springen. Stattdessen passen wir einfach den Wert an, den wir für das nächste Update berechnet haben: Wenn der PTS zu weit hinter der Audiozeit liegt, verdoppeln wir unsere geschätzte Verzögerung. Wenn der PTS der Spielzeit zu weit voraus ist, aktualisieren wir ihn einfach so schnell wie möglich. Nachdem wir die konfigurierte Aktualisierungs- oder Verzögerungszeit haben, werden wir sie mit der Uhr unseres Computers vergleichen und frame_timer laufen lassen . Dieser Frame-Timer fasst alle geschätzten Verzögerungen während der Filmwiedergabe zusammen. Mit anderen Worten, dieser frame_timer- Dies ist die Zeit, die angibt, wann das nächste Bild angezeigt werden soll. Wir fügen dem Frame-Timer einfach eine neue Verzögerung hinzu, vergleichen sie mit der Uhrzeit auf unserem Computer und verwenden diesen Wert, um das nächste Update zu planen. Dies kann etwas verwirrend sein. Lesen Sie den Code daher sorgfältig durch:void video_refresh_timer(void *userdata) {
VideoState *is = (VideoState *)userdata;
VideoPicture *vp;
double actual_delay, delay, sync_threshold, ref_clock, diff;
if(is->video_st) {
if(is->pictq_size == 0) {
schedule_refresh(is, 1);
} else {
vp = &is->pictq[is->pictq_rindex];
delay = vp->pts - is->frame_last_pts;
if(delay <= 0 || delay >= 1.0) {
delay = is->frame_last_delay;
}
is->frame_last_delay = delay;
is->frame_last_pts = vp->pts;
ref_clock = get_audio_clock(is);
diff = vp->pts - ref_clock;
sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;
if(fabs(diff) < AV_NOSYNC_THRESHOLD) {
if(diff <= -sync_threshold) {
delay = 0;
} else if(diff >= sync_threshold) {
delay = 2 * delay;
}
}
is->frame_timer += delay;
actual_delay = is->frame_timer - (av_gettime() / 1000000.0);
if(actual_delay < 0.010) {
actual_delay = 0.010;
}
schedule_refresh(is, (int)(actual_delay * 1000 + 0.5));
video_display(is);
if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_rindex = 0;
}
SDL_LockMutex(is->pictq_mutex);
is->pictq_size--;
SDL_CondSignal(is->pictq_cond);
SDL_UnlockMutex(is->pictq_mutex);
}
} else {
schedule_refresh(is, 100);
}
}
Wir führen mehrere Überprüfungen durch: Erstens stellen wir sicher, dass die Verzögerung zwischen dem aktuellen PTS und dem vorherigen PTS sinnvoll ist. Wenn keine Verzögerung erforderlich ist, stimmen Audio und Video zu diesem Zeitpunkt überein und verwenden nur die letzte Verzögerung. Dann stellen wir sicher, dass die Synchronisationsschwelle erfüllt ist, da eine perfekte Synchronisation niemals stattfindet. FFplay verwendet einen Wert von 0,01 für den Schwellenwert. Wir stellen auch sicher, dass der Synchronisationsschwellenwert niemals kleiner als die Intervalle zwischen den PTS-Werten ist. Stellen Sie schließlich den minimalen Aktualisierungswert auf 10 Millisekunden ein (tatsächlich sollten sie den Frame hier überspringen, aber machen wir uns darüber keine Sorgen).Wir haben der großen Struktur eine Reihe von Variablen hinzugefügt. Vergessen Sie also nicht, den Code zu überprüfen. Vergessen Sie auch nicht, den Frame-Timer und die anfängliche Verzögerung des vorherigen Frames in stream_component_open zu initialisieren : is->frame_timer = (double)av_gettime() / 1000000.0;
is->frame_last_delay = 40e-3;
Sync: Audiouhr
Es ist an der Zeit, die Audiouhr zu realisieren. Wir können die Zeit in unserer Funktion audio_decode_frame aktualisieren , in der wir das Audio dekodieren. Denken Sie jetzt daran, dass wir nicht jedes Mal, wenn wir diese Funktion aufrufen, ein neues Paket verarbeiten. Es gibt also zwei Bereiche, in denen Sie die Uhr aktualisieren müssen. Als erstes erhalten wir das neue Paket: Installieren Sie einfach die Sound Clock auf dem PTS-Paket. Wenn das Paket dann mehrere Frames enthält, sparen wir die Audiowiedergabezeit, indem wir die Anzahl der Samples zählen und diese mit einer bestimmten Sampling-Frequenz pro Sekunde multiplizieren. Also, wenn wir das Paket haben:
if(pkt->pts != AV_NOPTS_VALUE) {
is->audio_clock = av_q2d(is->audio_st->time_base)*pkt->pts;
}
Und sobald wir das Paket bearbeiten:
pts = is->audio_clock;
*pts_ptr = pts;
n = 2 * is->audio_st->codec->channels;
is->audio_clock += (double)data_size /
(double)(n * is->audio_st->codec->sample_rate);
Ein paar kleine Nuancen: Die Funktionsvorlage wurde geändert und enthält jetzt pts_ptr. Ändern Sie sie daher unbedingt . pts_ptr ist der Zeiger , dass wir sagen , verwenden die audio_callback Audiopaket pts . Dies wird beim nächsten Mal verwendet, um Audio mit Video zu synchronisieren.Jetzt können wir endlich unsere Funktion get_audio_clock implementieren . Es ist nicht so einfach , wie Sie den Wert bekommen wird -> audio_clock , wenn man darüber nachdenkt. Bitte beachten Sie, dass wir PTS-Audio jedes Mal einstellen, wenn wir es verarbeiten, aber wenn Sie sich die Funktion audio_callback ansehenEs wird einige Zeit dauern, bis alle Daten aus unserem Audiopaket in unseren Ausgabepuffer verschoben sind. Dies bedeutet, dass der Wert in unserer Audiouhr möglicherweise zu weit voraus ist. Daher müssen wir überprüfen, wie viel wir schreiben müssen. Hier ist der vollständige Code:double get_audio_clock(VideoState *is) {
double pts;
int hw_buf_size, bytes_per_sec, n;
pts = is->audio_clock;
hw_buf_size = is->audio_buf_size - is->audio_buf_index;
bytes_per_sec = 0;
n = is->audio_st->codec->channels * 2;
if(is->audio_st) {
bytes_per_sec = is->audio_st->codec->sample_rate * n;
}
if(bytes_per_sec) {
pts -= (double)hw_buf_size / bytes_per_sec;
}
return pts;
}
Du solltest jetzt verstehen, warum diese Funktion funktioniert;)Also, das war's! Wir kompilieren:gcc -o tutorial05 tutorial05.c -lavutil -lavformat -lavcodec -lswscale -lz -lm \
`sdl-config --cflags --libs`
Es passierte! Sie können den Film auf einem selbst erstellten Player ansehen. In der nächsten Lektion werden wir uns mit der Audiosynchronisation befassen und dann lernen, wie man sucht.Übersetzungen im Edison Blog: