/* * Copyright 2007-2008, Haiku. Stephan Aßmus * All rights reserved. Distributed under the terms of the MIT License. */ #include "MediaTrackVideoSupplier.h" #include #include #include #include #include "ColorSpaceToString.h" using std::nothrow; #define DEBUG_DECODED_FRAME 0 #if DEBUG_DECODED_FRAME # include # include # include # include #endif // DEBUG_DECODED_FRAME // constructor MediaTrackVideoSupplier::MediaTrackVideoSupplier(BMediaTrack* track, int32 trackIndex, status_t& initStatus) : VideoTrackSupplier() , fVideoTrack(track) , fPerformanceTime(0) , fDuration(0) , fCurrentFrame(0) , fTrackIndex(trackIndex) { if (!fVideoTrack) { printf("MediaTrackVideoSupplier() - no video track\n"); return; } initStatus = _SwitchFormat(B_NO_COLOR_SPACE, 0); fDuration = fVideoTrack->Duration(); // for (bigtime_t time = 0; time < fDuration; time += 10000) { // bigtime_t keyFrameTime = time; // fVideoTrack->FindKeyFrameForTime(&keyFrameTime, // B_MEDIA_SEEK_CLOSEST_BACKWARD); // printf("keyframe time for time: %lld -> %lld\n", time, keyFrameTime); // } } // destructor MediaTrackVideoSupplier::~MediaTrackVideoSupplier() { } const media_format& MediaTrackVideoSupplier::Format() const { return fFormat; } status_t MediaTrackVideoSupplier::GetEncodedFormat(media_format* format) const { if (!fVideoTrack) return B_NO_INIT; return fVideoTrack->EncodedFormat(format); } status_t MediaTrackVideoSupplier::GetCodecInfo(media_codec_info* info) const { if (!fVideoTrack) return B_NO_INIT; return fVideoTrack->GetCodecInfo(info); } status_t MediaTrackVideoSupplier::ReadFrame(void* buffer, bigtime_t* performanceTime, const media_format* format, bool& wasCached) { if (!fVideoTrack) return B_NO_INIT; if (!buffer) return B_BAD_VALUE; status_t ret = B_OK; if (format->u.raw_video.display.format != fFormat.u.raw_video.display.format || fFormat.u.raw_video.display.bytes_per_row != format->u.raw_video.display.bytes_per_row) { ret = _SwitchFormat(format->u.raw_video.display.format, format->u.raw_video.display.bytes_per_row); if (ret < B_OK) { fprintf(stderr, "MediaTrackVideoSupplier::ReadFrame() - " "unable to switch media format: %s\n", strerror(ret)); return ret; } } // read a frame int64 frameCount = 1; // TODO: how does this work for interlaced video (field count > 1)? media_header mediaHeader; ret = fVideoTrack->ReadFrames(buffer, &frameCount, &mediaHeader); if (ret < B_OK) { if (ret != B_LAST_BUFFER_ERROR) { fprintf(stderr, "MediaTrackVideoSupplier::ReadFrame() - " "error while reading frame of track: %s\n", strerror(ret)); } } else { fPerformanceTime = mediaHeader.start_time; } fCurrentFrame = fVideoTrack->CurrentFrame(); if (performanceTime) *performanceTime = fPerformanceTime; #if DEBUG_DECODED_FRAME if (modifiers() & B_SHIFT_KEY) { BFile fileStream("/boot/home/Desktop/decoded.png", B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE); BTranslatorRoster* roster = BTranslatorRoster::Default(); BBitmap* bitmap = new BBitmap(Bounds(), 0, ColorSpace(), BytesPerRow()); memcpy(bitmap->Bits(), buffer, bitmap->BitsLength()); BBitmapStream bitmapStream(bitmap); roster->Translate(&bitmapStream, NULL, NULL, &fileStream, B_PNG_FORMAT, 0); bitmapStream.DetachBitmap(&bitmap); delete bitmap; } #endif // DEBUG_DECODED_FRAME return ret; } status_t MediaTrackVideoSupplier::SeekToTime(bigtime_t* performanceTime) { if (!fVideoTrack) return B_NO_INIT; bigtime_t _performanceTime = *performanceTime; status_t ret = fVideoTrack->FindKeyFrameForTime(performanceTime, B_MEDIA_SEEK_CLOSEST_BACKWARD); if (ret < B_OK) return ret; ret = fVideoTrack->SeekToTime(performanceTime); if (ret == B_OK) { if (_performanceTime != *performanceTime) printf("seeked by time: %lld -> %lld\n", _performanceTime, *performanceTime); fPerformanceTime = *performanceTime; fCurrentFrame = fVideoTrack->CurrentFrame(); } return ret; } status_t MediaTrackVideoSupplier::SeekToFrame(int64* frame) { if (!fVideoTrack) return B_NO_INIT; int64 wantFrame = *frame; int64 currentFrame = fVideoTrack->CurrentFrame(); if (wantFrame == currentFrame) return B_OK; status_t ret = fVideoTrack->FindKeyFrameForFrame(frame, B_MEDIA_SEEK_CLOSEST_BACKWARD); if (ret < B_OK) return ret; if (*frame < currentFrame && wantFrame > currentFrame) { *frame = currentFrame; return B_OK; } if (wantFrame != *frame) { printf("seeked by frame: %lld -> %lld, was %lld\n", wantFrame, *frame, currentFrame); } ret = fVideoTrack->SeekToFrame(frame); if (ret == B_OK) { fCurrentFrame = *frame; fPerformanceTime = fVideoTrack->CurrentTime(); } return ret; } // #pragma mark - BRect MediaTrackVideoSupplier::Bounds() const { return BRect(0, 0, fFormat.u.raw_video.display.line_width - 1, fFormat.u.raw_video.display.line_count - 1); } color_space MediaTrackVideoSupplier::ColorSpace() const { return fFormat.u.raw_video.display.format; } uint32 MediaTrackVideoSupplier::BytesPerRow() const { return fFormat.u.raw_video.display.bytes_per_row; } // #pragma mark - status_t MediaTrackVideoSupplier::_SwitchFormat(color_space format, uint32 bytesPerRow) { // get the encoded format memset(&fFormat, 0, sizeof(media_format)); status_t ret = fVideoTrack->EncodedFormat(&fFormat); if (ret < B_OK) { printf("MediaTrackVideoSupplier::_SwitchFormat() - " "fVideoTrack->EncodedFormat(): %s\n", strerror(ret)); return ret; } // get ouput video frame size uint32 width = fFormat.u.encoded_video.output.display.line_width; uint32 height = fFormat.u.encoded_video.output.display.line_count; if (format == B_NO_COLOR_SPACE) { format = fFormat.u.encoded_video.output.display.format; if (format == B_NO_COLOR_SPACE) { // if still no preferred format, try the most commonly // supported overlay format format = B_YCbCr422; } else { printf("MediaTrackVideoSupplier::_SwitchFormat() - " "preferred color space: %s\n", color_space_to_string(format)); } } uint32 minBytesPerRow; if (format == B_YCbCr422) minBytesPerRow = ((width * 2 + 3) / 4) * 4; else minBytesPerRow = width * 4; bytesPerRow = max_c(bytesPerRow, minBytesPerRow); ret = _SetDecodedFormat(width, height, format, bytesPerRow); if (ret < B_OK) { printf("MediaTrackVideoSupplier::_SwitchFormat() - " "fVideoTrack->DecodedFormat(): %s - retrying with B_RGB32\n", strerror(ret)); format = B_RGB32; bytesPerRow = max_c(bytesPerRow, width * 4); ret = _SetDecodedFormat(width, height, format, bytesPerRow); if (ret < B_OK) { printf("MediaTrackVideoSupplier::_SwitchFormat() - " "fVideoTrack->DecodedFormat(): %s - giving up\n", strerror(ret)); return ret; } } if (fFormat.u.raw_video.display.format != format) { printf("MediaTrackVideoSupplier::_SwitchFormat() - " " codec changed colorspace of decoded format (%s -> %s)!\n", color_space_to_string(format), color_space_to_string(fFormat.u.raw_video.display.format)); // check if the codec forgot to adjust bytes_per_row format = fFormat.u.raw_video.display.format; if (format == B_YCbCr422) minBytesPerRow = ((width * 2 + 3) / 4) * 4; else minBytesPerRow = width * 4; if (minBytesPerRow > fFormat.u.raw_video.display.bytes_per_row) { printf(" -> stupid codec forgot to adjust bytes_per_row!\n"); ret = _SetDecodedFormat(width, height, format, minBytesPerRow); } } if (fFormat.u.raw_video.last_active != height - 1) { printf("should skip %ld lines at bottom!\n", (height - 1) - fFormat.u.raw_video.last_active); } return ret; } status_t MediaTrackVideoSupplier::_SetDecodedFormat(uint32 width, uint32 height, color_space format, uint32 bytesPerRow) { // specifiy the decoded format. we derive this information from // the encoded format (width & height). memset(&fFormat, 0, sizeof(media_format)); // fFormat.u.raw_video.last_active = height - 1; // fFormat.u.raw_video.orientation = B_VIDEO_TOP_LEFT_RIGHT; // fFormat.u.raw_video.pixel_width_aspect = 1; // fFormat.u.raw_video.pixel_height_aspect = 1; fFormat.u.raw_video.display.format = format; fFormat.u.raw_video.display.line_width = width; fFormat.u.raw_video.display.line_count = height; fFormat.u.raw_video.display.bytes_per_row = bytesPerRow; return fVideoTrack->DecodedFormat(&fFormat); }