Videos (mp4) abspielen

AckiB

Mitglied
hi,
ich möchte ein Programm schreiben, mit dem ich mp4 Videos abspielen kann.
Das Video soll in einem Steuerelement (hwnd) dargestellt weden.
Ich habe zwar die ffmpeg lib, aber weiß nicht, wie ich damit das Ganze über einen Handle (hwnd) bewerkstelligen soll/kann.
Hat jemand ein Tutorial, oder einen Link dazu?

Oder kann mir jemand eine andere (free) lib empfehlen, mit der man das bewerkstelligen kann?

Ich benutze CodeBlocks mit MinGW/GCC Compiler, System Win7 pro (64 Bit).

Danke, für eure Aufmerksamkeit,
Acki
 
Hallo AckiB

Die meisten Bibliotheken werden dir (wie vermutlich auch ffmpeg) einfach die Bilddaten für ein Frame als Speicher liefern, für die Darstellung bist du dann selber verantwortlich. Schau also am besten mal welche Ausgabemöglichkeiten zum Beispiel ffmpeg haben und dann musst du nur noch darstellen der Bilddaten machen (wenn sie bereits in RGB8 sind ist das ja keine Hexerei).

Viele Grüsse
Cromon
 
Danke für die schnelle Antwort!

Ich habe jetzt zu ffmpeg Tutorials gefunden...
Leider benutzen die zusätzlich SDL, welches ich aber nicht habe...

Muss ich mir Das wohl auch mal installieren und schauen, ob ich's dann hinbekomme...

Bin aber trotzdem für jeden weiteren Tip dankbar!

danke,
Acki
 
Hallo AckiB

Ich habe mal heute ein bisschen mit FFMPEG herumgespielt und für MP4 ohne SDL direkt in das Fenster zeichnen sozusagen ein PoC entwickelt mit ein paar Kommentaren:
C++:
#include <Windows.h>
#include <thread>
#include <mutex>
#include <future>
#include <gdiplus.h>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libswscale/swscale.h>
}

#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "swscale.lib")
#pragma comment(lib, "gdiplus.lib")

// wird in jedem frame neu gesetzt, sollte aber immer gleich bleiben
AVFrame* curFrame = nullptr;

// gleich wie bei curFrame
unsigned int frameWidth = 0;

// gleich wie bei curFrame
unsigned int frameHeight = 0;

// synchronisiert curFrame->data
std::mutex packetLock;

// handle des globalen Fensters
HWND hWnd;

// indikator für den decoder thread um so bald wie möglich zu beenden
std::atomic_bool isRunning;

void decodeThread(AVCodecContext* codecCtx, AVFormatContext* context, int videoStream) {
	// in videoFrame wird das encodierte Bild gespeichert
	AVFrame* videoFrame = av_frame_alloc();

	// in imageFrame wird videoFrame decodiert
	AVFrame* imageFrame = av_frame_alloc();

	// Dieses Format funktioniert bei meinem Video, keine Ahnung, ob das immer so ist für h264
	codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;

	// Wir wollen unser bild als 32bpp RGB haben, dafür erstellen wir hier einen puffer
	int size = avpicture_get_size(PIX_FMT_RGB32, codecCtx->width, codecCtx->height);
	unsigned char* buffer = (unsigned char*) av_malloc(size);

	// buffer an imageFrame geben
	avpicture_fill((AVPicture*) imageFrame, buffer, PIX_FMT_RGB32, codecCtx->width, codecCtx->height);

	// was den filter anbelangt, wie üblich: Genauigkeit vs Performance
	auto swsCtx = sws_getContext(codecCtx->width, codecCtx->height, codecCtx->pix_fmt, codecCtx->width, codecCtx->height, PIX_FMT_RGB32, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);

	AVPacket packet;
	while (isRunning && av_read_frame(context, &packet) >= 0) {
		if (packet.stream_index != videoStream) {
			av_free_packet(&packet);
			continue;
		}

		int hasImage = 0;
		avcodec_decode_video2(codecCtx, videoFrame, &hasImage, &packet);
		if (hasImage != 0) {

			packetLock.lock();
			// sobald wir im kritischen Bereich sind können wir den Inhalt von videoFrame dekodieren nach imageFrame
			sws_scale(swsCtx, videoFrame->data, videoFrame->linesize, 0, codecCtx->height, imageFrame->data, imageFrame->linesize);
			frameWidth = codecCtx->width;
			frameHeight = codecCtx->height;
			packetLock.unlock();

			curFrame = imageFrame;
			RECT dirtyRect = { 0 };
			GetClientRect(hWnd, &dirtyRect);
			// fenster neu zeichnen lassen
			InvalidateRect(hWnd, &dirtyRect, FALSE);
			UpdateWindow(hWnd);
		}

		av_free_packet(&packet);
	}

	av_frame_free(&videoFrame);
	av_frame_free(&imageFrame);
	av_free(buffer);
	sws_freeContext(swsCtx);
}

LRESULT CALLBACK WndProc(HWND hWindow, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	switch (uMsg) {
	case WM_CLOSE:
		PostQuitMessage(0);
		break;

	case WM_PAINT:
		{
			if (curFrame == nullptr) {
				 break;
			}

			PAINTSTRUCT ps;
			HDC hDc = BeginPaint(hWindow, &ps);

			Gdiplus::Bitmap bmp(frameWidth, frameHeight, PixelFormat32bppARGB);
			Gdiplus::Graphics* g = Gdiplus::Graphics::FromHDC(hDc);
			
			Gdiplus::BitmapData data;
			bmp.LockBits(nullptr, 0, PixelFormat32bppARGB, &data);
			packetLock.lock();
			memcpy(data.Scan0, curFrame->data[0], frameHeight * curFrame->linesize[0]);
			packetLock.unlock();
			bmp.UnlockBits(&data);

			RECT rcDst = { 0 };
			GetClientRect(hWindow, &rcDst);

			g->DrawImage(&bmp, 0, 0, rcDst.right - rcDst.left, rcDst.bottom - rcDst.top);
			g->Flush();

			EndPaint(hWindow, &ps);

			delete g;
		}
		return 0;
	}

	return DefWindowProc(hWindow, uMsg, wParam, lParam);
}

int main() {
	ULONG_PTR token;
	Gdiplus::GdiplusStartupInput input;
	Gdiplus::GdiplusStartup(&token, &input, nullptr);

	av_register_all();
	AVFormatContext* context = nullptr;
	auto res = avformat_open_input(&context, "Lush & Simon feat. Rico & Miella - Drag Me To The Ground (Official Music Video).mp4", nullptr, nullptr);
	if (res != 0) {
		return -1;
	}

	int videoStream = -1;
	for (int i = 0; i < context->nb_streams; ++i) {
		if (context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
			videoStream = i;
			break;
		}
	}

	if (videoStream < 0) {
		return -1;
	}

	AVCodecContext* codecCtx = context->streams[videoStream]->codec;

	AVCodec* codec = avcodec_find_decoder(codecCtx->codec_id);
	avcodec_open2(codecCtx, codec, nullptr);

	WNDCLASS wc = { 0 };
	wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
	wc.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
	wc.hInstance = GetModuleHandle(nullptr);
	wc.lpfnWndProc = WndProc;
	wc.lpszClassName = "x264WindowClass";
	wc.style = CS_HREDRAW | CS_VREDRAW;

	RegisterClass(&wc);

	hWnd = CreateWindow("x264WindowClass", "x264", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 960, 540, nullptr, nullptr, wc.hInstance, nullptr);

	ShowWindow(hWnd, SW_SHOW);
	isRunning = true;
	std::thread decoder(std::bind(&decodeThread, codecCtx, context, videoStream));

	MSG msg;
	while (GetMessage(&msg, nullptr, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	isRunning = false;
	decoder.join();

	avcodec_close(codecCtx);
	avformat_close_input(&context);
}

Das ist mehr so als Konzept gedacht, der Code ist nicht für einen produktiven Einsatz gedacht. Zudem müsste auch noch geschaut werden, ob wirklich alle C-Elemente aus libavcodec und libavformat freigegeben wurden. Das Beispiel funktioniert jedoch, siehe:
6ePCq.jpg

Viele Grüsse
Cromon
 
WOW, vielen, vielen Dank!
Ich denke, damit komme ich erstmal zurecht...
Die Hauptsache ist wohl, dass ich sehe, wie ich SDL umgehen kann... :D
Und dann zu versuchen das Ganze mit den anderen Tutorials zu verbinden...

Sollte ich noch Fragen haben, melde ich mich nochmal! ;)

Vielen Dank,
Acki
 
OK,
ich scheine es hinzubekommen...
allerdings könnte ich später noch Probleme mit dem Sound bekommen...
aber zuerst möchte ich verstehen, wie ffmpeg genau funktioniert... :D

also habe ich mir die Tutorials mal vorgenommen...
erstmal nur zum Kompilieren geschaft...
die Tutorials sind zwar out of date, können aber kompiliert werden...
allerdings ist der Sound in den letzten Tutorials nicht korrekt...
abgehackt, wie ein Maschinengewehr, und viel zu hoch (double pitch)...
aber, wie gesagt, darum werde ich mich später kümmern... :D

das eigentliche Problem, das ich im Moment habe, ist, dass ich es nicht schaffe den Code aus "main.c" auszulagern!?
also, ffmpeg ist pures C... noch kein Problem...
solange ich int main() in der selben Datei wie den ffmpeg Code habe...
lagere ich den Code in eine Funktion aus (also nicht in int main()), muss ich die Funktion statisch deklarieren, sonst schrottet die mir das Video, das ich nur "einlese"... seltsam, aber naja, geht auch noch klar...
ABER, versuche ich den ffmpeg Code in eine andere Datei auszulagern funktioniert gar nichts mehr!
obwohl ich die Funktionen deklariere (in 'ner .h Datei) bekomme ich nur "undefined references" (natürlich include ich die header Datei)!
ich habe absolut keine Ahnung, warum das nicht geht, bzw. wie ich das hinbekomme?!

hier ist der Code des ersten Tutorials, der funktioniert...
meinen Testcode, incl. ffmpeg lib, ist HIER...
Code:
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
  FILE *pFile;
  char szFilename[32];
  int  y;

  // Open file
  sprintf(szFilename, "pics\\frame%d.ppm", iFrame);
  pFile=fopen(szFilename, "wb");
  if(pFile==NULL)
    return;

  // Write header
  fprintf(pFile, "P6\n%d %d\n255\n", width, height);

  // Write pixel data
  for(y=0; y<height; y++)
    fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);

  // Close file
  fclose(pFile);
}

int main() {
  AVFormatContext *pFormatCtx;
  int             i, videoStream;
  AVCodecContext  *pCodecCtx;
  AVCodec         *pCodec;
  AVFrame         *pFrame;
  AVFrame         *pFrameRGB;
  AVPacket        packet;
  int             frameFinished;
  int             numBytes;
  uint8_t         *buffer;
  static struct SwsContext *img_convert_ctx;

   // Register all formats and codecs
  av_register_all();

  // Open video file
// \todo: fix program crash when file can't be loaded ******
  if(avformat_open_input(&pFormatCtx, vFile, NULL, NULL)!=0)
    return -1; // Couldn't open file

  // Retrieve stream information
  if(avformat_find_stream_info(pFormatCtx, NULL)<0)
    return -1; // Couldn't find stream information

  // Dump information about file onto standard error
  av_dump_format(pFormatCtx, 0, vFile, 0);


  // Find the first video stream
  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; // Didn't find a video stream


  // Get a pointer to the codec context for the video stream
  pCodecCtx=pFormatCtx->streams[videoStream]->codec;

  // Find the decoder for the video stream
  pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
  if(pCodec==NULL) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1; // Codec not found
  }

  // Open codec
  if(avcodec_open2(pCodecCtx, pCodec, NULL)<0)
    return -1; // Could not open codec

  // Allocate video frame
  pFrame=av_frame_alloc();

  // Allocate an AVFrame structure
  pFrameRGB=av_frame_alloc();
  if(pFrameRGB==NULL)
    return -1;

  // Determine required buffer size and allocate buffer
  numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
			      pCodecCtx->height);
  buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

  // Assign appropriate parts of buffer to image planes in pFrameRGB
  // Note that pFrameRGB is an AVFrame, but AVFrame is a superset
  // of AVPicture
  avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
		 pCodecCtx->width, pCodecCtx->height);

  // Read frames and save first five frames to disk
  i=0;
  img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
                                   pCodecCtx->width, pCodecCtx->height, PIX_FMT_RGB24,
                                   SWS_BICUBIC, NULL, NULL, NULL);
    while((av_read_frame(pFormatCtx, &packet)>=0) && (i<5)) {
        // Is this a packet from the video stream?
        if(packet.stream_index == videoStream) {

            /// Decode video frame
            //avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,packet.data, packet.size);
            avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

            // Did we get a video frame?
            if(frameFinished) {
                i++;
                sws_scale(img_convert_ctx, (const uint8_t * const *)pFrame->data,
                          pFrame->linesize, 0, pCodecCtx->height,
                          pFrameRGB->data, pFrameRGB->linesize);
                SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i);
            }
        }

        // Free the packet that was allocated by av_read_frame
        av_free_packet(&packet);
    }

  // Free the RGB image
  av_free(buffer);
  av_free(pFrameRGB);

  // Free the YUV frame
  av_free(pFrame);

  // Close the codec
  avcodec_close(pCodecCtx);

  // Close the video file
  avformat_close_input(&pFormatCtx);

  return 0;
}

also, meine Frage ist, wie kann ich den Code auslagern, bzw. mit C++ mischen?

danke,
Acki
 

Neue Beiträge

Zurück