SINT SoundSourceFFmpeg::seekSampleFrame(SINT frameIndex) { DEBUG_ASSERT(isValidFrameIndex(frameIndex)); int ret = 0; qint64 i = 0; if (frameIndex < 0 || frameIndex < m_lCacheStartFrame) { ret = avformat_seek_file(m_pFormatCtx, m_iAudioStream, 0, 32767 * 2, 32767 * 2, AVSEEK_FLAG_BACKWARD); if (ret < 0) { qDebug() << "SoundSourceFFmpeg::seek: Can't seek to 0 byte!"; return -1; } clearCache(); m_lCacheStartFrame = 0; m_lCacheEndFrame = 0; m_lCacheLastPos = 0; m_lCacheFramePos = 0; m_lStoredSeekPoint = -1; // Try to find some jump point near to // where we are located so we don't needed // to try guess it if (frameIndex >= AUDIOSOURCEFFMPEG_POSDISTANCE) { for (i = 0; i < m_SJumpPoints.size(); i ++) { if (m_SJumpPoints[i]->startFrame >= frameIndex && i > 2) { m_lCacheFramePos = m_SJumpPoints[i - 2]->startFrame * 2; m_lStoredSeekPoint = m_SJumpPoints[i - 2]->pos; break; } } } if (frameIndex == 0) { readFramesToCache((AUDIOSOURCEFFMPEG_CACHESIZE - 50), -1); } else { readFramesToCache((AUDIOSOURCEFFMPEG_CACHESIZE / 2), frameIndex); } } if (m_lCacheEndFrame <= frameIndex) { readFramesToCache(100, frameIndex); } m_currentMixxxFrameIndex = frameIndex; m_bIsSeeked = TRUE; return frameIndex; }
SINT SoundSourceFFmpeg::seekSampleFrame(SINT frameIndex) { DEBUG_ASSERT(isValidFrameIndex(frameIndex)); int ret = 0; qint64 i = 0; struct ffmpegLocationObject *l_STestObj = nullptr; if (frameIndex < 0 || frameIndex < m_lCacheStartFrame) { // Seek to set (start of the stream which is FFmpeg frame 0) // because we are dealing with compressed audio FFmpeg takes // best of to seek that point (in this case 0 Is always there) // in every other case we should provide MIN and MAX tolerance // which we can take. // FFmpeg just just can't take zero as MAX tolerance so we try to // just make some tolerable (which is never used because zero point // should always be there) some number (which is 0xffff 65535) // that is chosen because in WMA frames can be that big and if it's // smaller than the frame we are seeking we can get into error ret = avformat_seek_file(m_pFormatCtx, m_iAudioStream, 0, 0, 0xffff, AVSEEK_FLAG_BACKWARD); if (ret < 0) { qDebug() << "SoundSourceFFmpeg::seek: Can't seek to 0 byte!"; return -1; } clearCache(); m_lCacheStartFrame = 0; m_lCacheEndFrame = 0; m_lCacheLastPos = 0; m_lCacheFramePos = 0; m_lStoredSeekPoint = -1; // Try to find some jump point near to // where we are located so we don't needed // to try guess it if (m_SJumpPoints.size() > 0) { l_STestObj = m_SJumpPoints.first(); if (frameIndex > l_STestObj->startFrame) { for (i = 0; i < m_SJumpPoints.size(); i++) { if (m_SJumpPoints[i]->startFrame >= frameIndex) { if (i > 0) { i--; } m_lCacheFramePos = m_SJumpPoints[i]->startFrame; m_lStoredSeekPoint = m_SJumpPoints[i]->pos; m_SStoredJumpPoint = m_SJumpPoints[i]; break; } } } } if (frameIndex == 0) { // Because we are in the beginning just read cache full // but leave 50 of just in case // -1 one means we are seeking from current position and // filling the cache readFramesToCache((AUDIOSOURCEFFMPEG_CACHESIZE - 50), AUDIOSOURCEFFMPEG_FILL_FROM_CURRENTPOS); } } if (m_lCacheEndFrame <= frameIndex) { // Cache tries to read until it gets to frameIndex // after that we still read 100 FFmpeg frames to memory // so we have good cache to go forward (100) and backward (900) // from the point readFramesToCache(100, frameIndex); } m_currentMixxxFrameIndex = frameIndex; m_bIsSeeked = true; return frameIndex; }
bool SoundSourceFFmpeg::getBytesFromCache(CSAMPLE* buffer, SINT offset, SINT size) { struct ffmpegCacheObject *l_SObj = nullptr; qint32 l_lPos = 0; quint32 l_lLeft = AUDIOSOURCEFFMPEG_MIXXXFRAME_TO_BYTEOFFSET(size); quint32 l_lOffset = 0; quint32 l_lBytesToCopy = 0; bool l_bEndOfFile = false; char *l_pBuffer = (char *)buffer; // If cache is empty then retun without crash. if (m_SCache.isEmpty()) { qDebug() << "SoundSourceFFmpeg::getBytesFromCache: Cache is empty can't return bytes"; memset(l_pBuffer, 0x00, l_lLeft); return false; } // Is offset bigger than start of cache if (offset >= m_lCacheStartFrame) { int l_lTmpLen = 0; // If last pos is (which is shouldn't) use caches end if (m_lCacheLastPos == 0) { m_lCacheLastPos = m_SCache.size() - 1; } // Seek to correct FrameIndex (Minus 5 for faster seek) // // This could be done per steps but because there // Jump points can be far away and codec frames are small // just jump to point where is safe to start. for (l_lPos = m_lCacheLastPos; l_lPos >= 0; l_lPos -= 5) { l_SObj = m_SCache[l_lPos]; // Because length is in byte we have to convert it to Frames l_lTmpLen = AUDIOSOURCEFFMPEG_BYTEOFFSET_TO_MIXXXFRAME(l_SObj->length); if ((l_SObj->startFrame + l_lTmpLen) < offset) { break; } } // Because we step 5 backward we can end up to below zero // We can't go futher so hope for the best if (l_lPos < 0) { l_lPos = 0; } // This shouldn't never happen.. because it's nearly imposible // but because it can happen double check if (l_lPos >= m_SCache.size()) { l_lPos = m_SCache.size() - 1; } // Use this Cache object as starting point l_SObj = m_SCache[l_lPos]; if (l_SObj == nullptr) { qDebug() << "SoundSourceFFmpeg::getBytesFromCache: Cache object nullptr"; return false; } if (l_pBuffer == nullptr) { qDebug() << "SoundSourceFFmpeg::getBytesFromCache: Out buffer nullptr"; return false; } while (l_lLeft > 0) { // If Cache is running low read more if ((l_lPos + 5) > m_SCache.size() && l_bEndOfFile == false) { offset = l_SObj->startFrame; // Read 50 frames from current pos. If we hit file end before that // exit if (readFramesToCache(50, AUDIOSOURCEFFMPEG_FILL_FROM_CURRENTPOS) == false) { // File has ended.. don't try to cache anymore // or some fatal error has occurred so.. just don't l_bEndOfFile = true; } // Seek back to correct place for (l_lPos = (m_SCache.size() - 50); l_lPos > 0; l_lPos--) { l_SObj = m_SCache[l_lPos]; if ((l_SObj->startFrame + l_SObj->length) < offset) { break; } } if (l_lPos < m_SCache.size() && l_lPos >= 0) { l_SObj = m_SCache[l_lPos]; continue; } else if (l_lPos < 0) { l_lPos = 0; } else { l_SObj = m_SCache.last(); l_lPos = l_lPos < m_SCache.size() - 1; } } // If Cache object ain't correct then calculate offset if (l_SObj->startFrame <= offset) { // We have to convert again it to bytes l_lOffset = AUDIOSOURCEFFMPEG_MIXXXFRAME_TO_BYTEOFFSET(offset - l_SObj->startFrame); } // Okay somehow offset is bigger than our Cache object have bytes if (l_lOffset >= l_SObj->length) { if ((l_lPos + 1) < m_SCache.size()) { l_SObj = m_SCache[++ l_lPos]; continue; } else { qDebug() << "SoundSourceFFmpeg::getBytesFromCache: Buffer run out. Shouldn't happen!"; memset(l_pBuffer, 0x00, l_lLeft); return false; } } // If bytes left is bigger than FFmpeg frame bytes available // then copy to buffer end and then jump to next FFmpeg frame // to understand this here are some examples // * MP3 have size 2304 * 4 // * OGG/Opus size 256 - 1024 // * WMA size 32767 - 131070 // and all these are separated in packets nor solid stream of bytes // that just can be copied to buffer // so that's why this kind of abstraction is needed if (l_lLeft > (l_SObj->length - l_lOffset)) { // calculate start point of copy l_lBytesToCopy = l_SObj->length - l_lOffset; memcpy(l_pBuffer, (l_SObj->bytes + l_lOffset), l_lBytesToCopy); l_lOffset = 0; l_pBuffer += l_lBytesToCopy; l_lLeft -= l_lBytesToCopy; } else { memcpy(l_pBuffer, (l_SObj->bytes + l_lOffset), l_lLeft); l_lLeft = 0; } // If we have more items of cache use them // or after that just zero buffer.. if ((l_lPos + 1) < m_SCache.size()) { l_SObj = m_SCache[++ l_lPos]; } else { // With MP3 VBR length of audio is just a guess // it's near good as it can get but it can be too long // so fill buffer with 0x00 (zero) that we don't get ugly // noise at the end of the file memset(l_pBuffer, 0x00, l_lLeft); l_lLeft = 0; } } m_lCacheLastPos = --l_lPos; return true; } return false; }
bool SoundSourceFFmpeg::getBytesFromCache(char *buffer, SINT offset, SINT size) { struct ffmpegCacheObject *l_SObj = NULL; quint32 l_lPos = 0; quint32 l_lLeft = 0; quint32 l_lOffset = 0; quint32 l_lBytesToCopy = 0; // Is offset bigger than start of cache if (offset >= m_lCacheStartFrame) { // If last pos is (which is shouldn't) use caches end if (m_lCacheLastPos == 0) { m_lCacheLastPos = m_SCache.size() - 1; } // Seek to correct FrameIndex for (l_lPos = m_lCacheLastPos; l_lPos > 0; l_lPos --) { l_SObj = m_SCache[l_lPos]; if ((l_SObj->startFrame + l_SObj->length) < offset) { break; } } // Use this Cache object as starting point l_SObj = m_SCache[l_lPos]; // Calculate in other words get bytes how much we must copy to // buffer (CSAMPLE = 4 and we have 2 channels which is 8 times) l_lLeft = (size * sizeof(CSAMPLE)) * 2; memset(buffer, 0x00, l_lLeft); while (l_lLeft > 0) { // If Cache is running low read more if (l_SObj == NULL || (l_lPos + 5) > (unsigned int)m_SCache.size()) { offset = l_SObj->startFrame; if (readFramesToCache(50, -1) == false) { return false; } // Seek back to correct place for (l_lPos = (m_SCache.size() - 50); l_lPos > 0; l_lPos --) { l_SObj = m_SCache[l_lPos]; if ((l_SObj->startFrame + l_SObj->length) < offset) { break; } } l_SObj = m_SCache[l_lPos]; continue; } // If Cache object ain't correct then calculate offset if (l_SObj->startFrame <= offset) { // We have to convert again it to bytes l_lOffset = (offset - l_SObj->startFrame) * (sizeof(CSAMPLE) * 2); } // Okay somehow offset is bigger than our Cache object have bytes if (l_lOffset >= l_SObj->length) { l_SObj = m_SCache[++ l_lPos]; continue; } if (l_lLeft > l_SObj->length) { // calculate start point of copy l_lBytesToCopy = l_SObj->length - l_lOffset; memcpy(buffer, (l_SObj->bytes + l_lOffset), l_lBytesToCopy); l_lOffset = 0; buffer += l_lBytesToCopy; l_lLeft -= l_lBytesToCopy; } else { memcpy(buffer, l_SObj->bytes, l_lLeft); l_lLeft = 0; } l_SObj = m_SCache[++ l_lPos]; } m_lCacheLastPos = --l_lPos; return true; } return false; }